Qual é o propósito do: (cólon) GNU Bash embutido?

Qual é o propósito de um comando que não faz nada, sendo pouco mais do que um líder de comentário, mas na verdade é um shell embutido em si mesmo?

É mais lento do que inserir um comentário em seus scripts em cerca de 40% por chamada, o que provavelmente varia muito dependendo do tamanho do comentário. As únicas razões possíveis que posso ver por isso são estas:

# poor man's delay function for ((x=0;x<100000;++x)) ; do : ; done # inserting comments into string of commands command ; command ; : we need a comment in here for some reason ; command # an alias for `true' (lazy programming) while : ; do command ; done 

Eu acho que o que eu realmente estou procurando é o que a aplicação histórica poderia ter tido.

   

Historicamente , os shells de Bourne não eram true e false como comandos embutidos. true foi, em vez disso, simplesmente aliased para : e false para algo como let 0 .

: é um pouco melhor do que o true para portabilidade para antigos shells derivados de Bourne. Como um exemplo simples, considere não ter nem o ! operador de oleoduto nem o || operador de lista (como foi o caso de algumas antigas conchas Bourne). Isso deixa a cláusula else da instrução if como o único meio de ramificação com base no status de saída:

 if command; then :; else ...; fi 

Como if requer uma cláusula e comentários não vazios, then não conte como não vazios : serve como não-op.

Hoje em dia (isto é: em um contexto moderno) você geralmente pode usar : ou true . Ambos são especificados por POSIX, e alguns acham true mais fácil de ler. No entanto, há uma diferença interessante : é um chamado built-in especial POSIX, enquanto true é um built-in regular .

  • Especial built-ins são necessários para ser construído no shell; Os built-ins regulares são apenas “tipicamente” integrados, mas não são estritamente garantidos. Normalmente não deve haver um programa comum chamado : com a function true no PATH da maioria dos sistemas.

  • Provavelmente, a diferença mais crucial é que, com os built-ins especiais, qualquer variável definida pelo built-in – mesmo no ambiente durante a avaliação simples do comando – persiste após o comando ser concluído, conforme demonstrado aqui usando ksh93:

     $ unset x; ( x=hi :; echo "$x" ) hi $ ( x=hi true; echo "$x" ) $ 

    Note que o Zsh ignora este requisito, assim como o GNU Bash, exceto quando está operando no modo de compatibilidade POSIX, mas todos os outros principais shells “POSIX sh derived” observam isso, incluindo traço, ksh93 e mksh.

  • Outra diferença é que os built-ins regulares devem ser compatíveis com exec – demonstrado aqui usando o Bash:

     $ ( exec : ) -bash: exec: :: not found $ ( exec true ) $ 
  • O POSIX também observa explicitamente que : pode ser mais rápido que true , embora este seja, obviamente, um detalhe específico da implementação.

Eu uso para ativar / desativar facilmente comandos de variables:

 #!/bin/bash if [[ "$VERBOSE" == "" || "$VERBOSE" == "0" ]]; then vecho=":" # no "verbose echo" else vecho=echo # enable "verbose echo" fi $vecho "Verbose echo is ON" 

portanto

 $ ./vecho $ VERBOSE=1 ./vecho Verbose echo is ON 

Isso contribui para um script limpo. Isso não pode ser feito com ‘#’.

Além disso,

 : >afile 

é uma das maneiras mais simples de garantir que ‘afile’ existe, mas tem comprimento 0.

Uma aplicação útil para: é se você está interessado apenas em usar expansões de parâmetros para seus efeitos colaterais ao invés de realmente passar o resultado para um comando. Nesse caso, você usa o PE como um argumento para: ou false, dependendo se você deseja um status de saída de 0 ou 1. Um exemplo pode ser : "${var:=$1}" . Desde : é um builtin deve ser muito rápido.

: também pode ser para comentário de bloco (semelhante a / * * / na linguagem C). Por exemplo, se você quiser pular um bloco de código no seu script, você pode fazer isso:

 : < < 'SKIP' your code block here SKIP 

Se você quiser truncar um arquivo para zero bytes, útil para limpar logs, tente isto:

 :> file.log 

É semelhante a pass em Python.

Um uso seria descartar uma function até que ela seja escrita:

 future_function () { :; } 

Mais dois usos não mencionados em outras respostas:

Exploração madeireira

Pegue este script de exemplo:

 set -x : Logging message here example_command 

A primeira linha, set -x , faz com que o shell imprima o comando antes de executá-lo. É uma construção bastante útil. A desvantagem é que o tipo de echo Log message usual da instrução agora imprime a mensagem duas vezes. O método do cólon contorna isso. Note que você ainda terá que escaping de caracteres especiais exatamente como faria para o echo .

Títulos de emprego Cron

Eu vi isso sendo usado em tarefas cron, assim:

 45 10 * * * : Backup for database ; /opt/backup.sh 

Esta é uma tarefa cron que executa o script /opt/backup.sh todos os dias às 10:45. A vantagem dessa técnica é que ela melhora a aparência de assuntos de email quando o /opt/backup.sh imprime alguma saída.

Você poderia usá-lo em conjunto com backticks ( `` ) para executar um comando sem exibir sua saída, assim:

 : `some_command` 

É claro que você poderia apenas fazer some_command > /dev/null , mas o : -version é um pouco mais curto.

Dito isto, eu não recomendaria realmente fazer isso, pois apenas confundiria as pessoas. Apenas me veio à mente como um possível caso de uso.

Também é útil para programas poliglotas:

 #!/usr/bin/env sh ':' //; exec "$(command -v node)" "$0" "$@" ~function(){ ... } 

Isso agora é um script de shell executável e um programa JavaScript: significando ./filename.js , sh filename.js e node filename.js todo o trabalho.

(Definitivamente um pouco de uso estranho, mas eficaz, no entanto.)


Algumas explicações, conforme solicitado:

  • Scripts shell são avaliados linha por linha; e o comando exec , quando executado, encerra o shell e substitui seu processo pelo comando resultante. Isso significa que para o shell, o programa se parece com isso:

     #!/usr/bin/env sh ':' //; exec "$(command -v node)" "$0" "$@" 
  • Contanto que nenhuma expansão de parâmetro ou aliasing esteja ocorrendo na palavra, qualquer palavra em um script de shell pode ser envolvida por aspas sem alterar seu significado; isso significa que ':' é equivalente a : (nós apenas colocamos aspas aqui para obter a semântica do JavaScript descrita abaixo)

  • … e como descrito acima, o primeiro comando na primeira linha é um não-op (se traduz para : // , ou se você preferir citar as palavras, ':' '//' . Observe que // não traz nenhum significado especial aqui, como acontece em JavaScript; é apenas uma palavra sem significado que está sendo jogada fora.)

  • Finalmente, o segundo comando na primeira linha (após o ponto-e-vírgula) é a parte real do programa: é a chamada exec que substitui o shell script sendo chamado , com um processo chamado Node.js para avaliar o restante do script .

  • Enquanto isso, a primeira linha, em JavaScript, é analisada como uma string literal ( ':' ) e, em seguida, um comentário, que é excluído; Assim, para JavaScript, o programa se parece com isso:

     ':' ~function(){ ... } 

    Como o string-literal está em uma linha por si só, é uma instrução sem operação e, portanto, é retirado do programa; isso significa que a linha inteira é removida, deixando apenas o código do programa (neste exemplo, o corpo da function(){ ... } .)

Funções de auto-documentação

Você também pode usar : para incorporar a documentação em uma function.

Suponha que você tenha um script de biblioteca mylib.sh , fornecendo uma variedade de funções. Você poderia . mylib.sh a biblioteca ( . mylib.sh ) e chamar as funções diretamente depois disso ( lib_function1 arg1 arg2 ), ou evitar confundir seu namespace e invocar a biblioteca com um argumento de function ( mylib.sh lib_function1 arg1 arg2 ).

Não seria legal se você também pudesse digitar mylib.sh --help e obter uma lista de funções disponíveis e seu uso, sem precisar manter manualmente a lista de funções no texto de ajuda?

 #! / bin / bash

 # todas as funções "públicas" devem começar com este prefixo
 LIB_PREFIX = 'lib_'

 # funções da biblioteca "pública"
 lib_function1 () {
     : Esta function faz algo complicado com dois argumentos.
     :
     : parameters:
     : 'arg1 - primeiro argumento ($ 1)'
     : 'arg2 - segundo argumento'
     :
     Resultado:
     : " é complicado"

     # código de function real começa aqui
 }

 lib_function2 () {
     : Documentação de function

     # código de function aqui
 }

 # function de ajuda
 --Socorro() {
     echo MyLib v0.0.1
     eco
     echo Uso: mylib.sh [function_name [args]]
     eco
     echo Funções disponíveis:
     declare -f |  sed -n -e '/ ^' $ LIB_PREFIX '/, / ^} $ / {/ \ (^' $ LIB_PREFIX '\) \ | \ (^ [\ t] *: \) / {
         s / ^ \ ('$ LIB_PREFIX'. * \) () / \ n === \ 1 === /; s / ^ [\ t] *: \? ['\' '"] \? / / ; s / ['\' '"] \?; \? $ //; p}}'
 }

 # Código principal
 if ["$ {BASH_SOURCE [0]}" = "$ {0}"];  então
     # o script foi executado em vez de originado
     # invocar function solicitada ou exibir ajuda
     if ["$ (tipo -t -" $ 1 "2> / dev / null)" = function];  então
         "$ @"
     outro
         --Socorro
     fi
 fi

Alguns comentários sobre o código:

  1. Todas as funções “públicas” têm o mesmo prefixo. Apenas estes devem ser invocados pelo usuário e serem listados no texto de ajuda.
  2. O recurso de autodocumentação depende do ponto anterior e usa declare -f para enumerar todas as funções disponíveis e, em seguida, filtra-as por meio do sed para exibir apenas funções com o prefixo apropriado.
  3. É uma boa ideia include a documentação entre aspas simples, para impedir a expansão indesejada e a remoção de espaço em branco. Você também precisa ter cuidado ao usar apóstrofos / citações no texto.
  4. Você poderia escrever código para internalizar o prefixo da biblioteca, isto é, o usuário só precisa digitar mylib.sh function1 e ele será traduzido internamente para lib_function1 . Este é um exercício deixado ao leitor.
  5. A function de ajuda é chamada “–help”. Essa é uma abordagem conveniente (ou seja, lenta) que usa o mecanismo de invocação de biblioteca para exibir a própria ajuda, sem precisar codificar uma verificação extra por $1 . Ao mesmo tempo, ele irá confundir seu namespace se você originar a biblioteca. Se você não gosta disso, você pode alterar o nome para algo como lib_help ou realmente verificar os argumentos para --help no código principal e invocar a function de ajuda manualmente.

Eu vi esse uso em um script e pensei que era um bom substituto para invocar basename dentro de um script.

 oldIFS=$IFS IFS=/ for basetool in $0 ; do : ; done IFS=$oldIFS 

… isto é um substituto para o código: basetool=$(basename $0)