test - ubuntu bash source code




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

Funções de auto-documentação

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

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 ( lib_function1 arg1 arg2 ), ou evitar sobrecarregar seu namespace e invocar a biblioteca com um argumento de função ( 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

# all "public" functions must start with this prefix
LIB_PREFIX='lib_'

# "public" library functions
lib_function1() {
    : This function does something complicated with two arguments.
    :
    : Parameters:
    : '   arg1 - first argument ($1)'
    : '   arg2 - second argument'
    :
    : Result:
    : "   it's complicated"

    # actual function code starts here
}

lib_function2() {
    : Function documentation

    # function code here
}

# help function
--help() {
    echo MyLib v0.0.1
    echo
    echo Usage: mylib.sh [function_name [args]]
    echo
    echo Available functions:
    declare -f | sed -n -e '/^'$LIB_PREFIX'/,/^}$/{/\(^'$LIB_PREFIX'\)\|\(^[ \t]*:\)/{
        s/^\('$LIB_PREFIX'.*\) ()/\n=== \1 ===/;s/^[ \t]*: \?['\''"]\?/    /;s/['\''"]\?;\?$//;p}}'
}

# main code
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
    # the script was executed instead of sourced
    # invoke requested function or display help
    if [ "$(type -t - "$1" 2>/dev/null)" = function ]; then
        "[email protected]"
    else
        --help
    fi
fi

Alguns comentários sobre o código:

  1. Todas as funções "públicas" têm o mesmo prefixo. Somente 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 incluir 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ê pode 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 função de ajuda é chamada "--help". Essa é uma abordagem conveniente (ou seja, lenta) que usa o mecanismo de invocação da biblioteca para exibir a própria ajuda, sem precisar codificar uma verificação extra por $1 . Ao mesmo tempo, ele irá sobrecarregar o 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 função de ajuda manualmente.

Qual é o propósito de um comando que não faz nada, sendo pouco mais 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 pelas quais posso ver 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 é a aplicação histórica que poderia ter tido.


É semelhante a pass em Python.

Um uso seria eliminar uma função até que ela seja escrita:

future_function () { :; }

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  

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


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 agora imprime a mensagem duas vezes. O método do cólon contorna isso. Note que você ainda terá que escapar de caracteres especiais como faria para o echo .

Títulos do trabalho Cron

Eu vi isso sendo usado em tarefas cron, como este:

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 dos assuntos de email quando o /opt/backup.sh imprime alguma saída.


Também é útil para programas poliglotas:

#!/usr/bin/env sh
':' //; exec "$(command -v node)" "$0" "[email protected]"
~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.)

Alguma explicação, 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" "[email protected]"
  • 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 no-op (se traduz em : // , ou se você preferir citar as palavras, ':' '//' . Observe que // não traz nenhum significado especial aqui, como acontece em JavaScript; é apenas uma palavra sem sentido 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 script de shell que está 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(){ ... } .)


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 seu 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

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 || lista operador (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. Geralmente, não deve haver um programa comum chamado : com a função true no PATH da maioria dos sistemas.

  • Provavelmente, a diferença mais crucial é que, com 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 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 isso seja um detalhe específico da implementação.





built-in