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

335

Qual é o propósito de um comando que não faz nada, sendo pouco mais que um líder de comentários, 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 são:

# 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

Acho que o que realmente estou procurando é qual aplicativo histórico ele poderia ter.

anfetamaquina
fonte
5
Mesma pergunta no Unix e Linux : a que finalidade serve o cólon?
Caleb em
68
@ Caleb - eu perguntei isso dois anos antes daquele.
Amphetamachine
Eu não diria que um comando que retorna um valor específico "não faz nada". A menos que a programação funcional consista em "não fazer nada". :-)
LarsH

Respostas:

415

Historicamente , os reservatórios Bourne não tinham truee falsecomo comandos embutidos. trueera simplesmente um alias para :, e falsepara algo parecido let 0.

:é ligeiramente melhor do que truea portabilidade para conchas antigas derivadas de Bourne. Como um exemplo simples, considere não ter o !operador de pipeline nem o ||operador de lista (como foi o caso de algumas conchas Bourne antigas). Isso deixa a elsecláusula da ifdeclaração como o único meio de ramificação com base no status de saída:

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

Como ifrequer uma thencláusula não vazia e os comentários não contam como não vazios, :serve como não operacional.

Hoje em dia (ou seja: em um contexto moderno), geralmente você pode usar um :ou outro true. Ambos são especificados pelo POSIX, e alguns acham truemais fácil de ler. No entanto, há uma diferença interessante: :é o chamado embutido especial POSIX , enquanto o embutidotrue é regular .

  • É necessário que os embutidos especiais sejam incorporados ao shell; Os embutidos regulares são apenas "tipicamente" embutidos, mas não são estritamente garantidos. Normalmente, não deve haver um programa regular nomeado :com a função truePATH na maioria dos sistemas.

  • Provavelmente a diferença mais crucial é que, com embutidos especiais, qualquer variável definida pelo embutido - mesmo no ambiente durante uma simples avaliação de comando - persiste após a conclusão do comando, conforme demonstrado aqui usando o ksh93:

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

    Observe que o Zsh ignora esse requisito, assim como o GNU Bash, exceto quando opera no modo de compatibilidade com POSIX, mas todos os outros shells principais "derivados do POSIX sh" observam isso, incluindo dash, ksh93 e mksh.

  • Outra diferença é que os embutidos 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 esse seja, obviamente, um detalhe específico da implementação.

conde
fonte
Você quis dizer que os built-ins regulares não devem ser compatíveis exec?
Old Pro
11
@ OldPro: Não, ele está correto, pois trueé um builtin regular, mas ele está incorreto no que execestá usando ao /bin/trueinvés do builtin.
Pausado até novo aviso.
11
@DennisWilliamson Eu estava apenas seguindo o caminho que as especificações estão formuladas. É claro que a implicação é que os componentes internos regulares também devem ter uma versão independente.
Ormaaj
17
+1 Excelente resposta. Eu ainda gostaria de observar o uso para inicializar variáveis, como : ${var?not initialized}et al.
Tripleee
Um acompanhamento mais ou menos independente. Você disse que :é um especial incorporado e não deve ter uma função nomeada por ele. Mas não é o exemplo mais comum de fork pump :(){ :|: & };:nomear uma função com nome :?
Chong
63

Eu o uso para ativar / desativar facilmente comandos variáveis:

#!/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 cria um script limpo. Isso não pode ser feito com '#'.

Além disso,

: >afile

é uma das maneiras mais simples de garantir que 'afile' exista, mas tenha 0 comprimento.

Kevin Little
fonte
20
>afileé ainda mais simples e alcança o mesmo efeito.
earl
2
Legal, vou usar esse truque $ vecho para simplificar os scripts que estou mantendo.
BarneySchmale
5
Qual é o benefício de citar dois pontos vecho=":"? Apenas por legibilidade?
leoj
56

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

ormaaj
fonte
2
Você também pode usá-lo para efeitos colaterais da expansão aritmética: : $((a += 1))( ++e os --operadores não precisam ser implementados de acordo com o POSIX.). No bash, ksh e outros shells possíveis, você também pode fazer: ((a += 1))ou ((a++))mas não é especificado pelo POSIX.
Pabouk
@pabouk Sim, isso é verdade, embora (())seja especificado como um recurso opcional. "Se uma sequência de caracteres iniciada com" (("seria analisada pelo shell como uma expansão aritmética se precedida de um '$'), os shells que implementam uma extensão pela qual" ((expressão)) "são avaliados como uma expressão aritmética podem tratar o "((" como introdução como uma avaliação aritmética em vez de um comando de agrupamento. ")
ormaaj
50

:também pode ser para comentários em bloco (semelhante a / * * / no idioma C). Por exemplo, se você quiser pular um bloco de código no seu script, poderá fazer o seguinte:

: << 'SKIP'

your code block here

SKIP
zagpoint
fonte
3
Péssima ideia. Quaisquer substituições de comando dentro do documento aqui ainda são processadas.
chepner
33
Não é uma má idéia. Você pode evitar a resolução / substituição de variáveis ​​nos documentos aqui, citando o delimitador entre aspas simples:: << 'SKIP'
Rondo
11
IIRC você também pode \ escapar qualquer um dos caracteres delimitadores para o mesmo efeito: : <<\SKIP.
yyny 31/07/19
31

Se você deseja truncar um arquivo para zero bytes, útil para limpar logs, tente o seguinte:

:> file.log
Atum Ahi
fonte
16
> file.logé mais simples e alcança o mesmo efeito.
Amphetamachine
55
Sim, mas o rosto feliz é o que faz isso por mim:>
Ahi Tuna
23
@ amphetamachine: :>é mais portátil. Alguns shells (como o meu zsh) instanciam automaticamente um gato no shell atual e escutam o stdin quando recebem um redirecionamento sem nenhum comando. Em vez de cat /dev/null, :é muito mais simples. Geralmente, esse comportamento é diferente em shells interativos em vez de scripts, mas se você escrever o script de uma maneira que também funcione de maneira interativa, a depuração por copiar e colar será muito mais fácil.
Caleb em
2
Qual é a : > filediferença de true > file(além da contagem de caracteres e do rosto feliz) em uma concha moderna (assumindo :e truesendo igualmente rápidos)?
Adam Katz
30

É semelhante ao passPython.

Um uso seria remover uma função até que ela seja gravada:

future_function () { :; }
Pausado até novo aviso.
fonte
29

Mais dois usos não mencionados em outras respostas:

Exploração madeireira

Veja 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 echo Log messagetipo usual de instrução agora imprime a mensagem duas vezes. O método do cólon contorna isso. Observe que você ainda terá que escapar de caracteres especiais como faria echo.

Cargos Cron

Eu já vi isso sendo usado em tarefas cron, assim:

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

Este é um trabalho cron que executa o script /opt/backup.shtodos os dias às 10:45. A vantagem dessa técnica é que ela cria temas de e-mail com melhor aparência quando a /opt/backup.shimpressão é impressa.

Flimm
fonte
Onde é o local padrão do log? Posso definir o local do log? O objetivo é mais criar a saída no stdout durante scripts / processos em segundo plano?
13139 domdambrogia
11
@domdambrogia Ao usar set -x, os comandos impressos (incluindo algo como : foobar) vão para o stderr.
Flimm
22

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

: `some_command`

Claro que você poderia fazer some_command > /dev/null, mas o: versão-é um pouco mais curta.

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

sepp2k
fonte
25
Isso não é seguro se o comando despejar alguns megabytes de saída, pois o shell armazena em buffer a saída e a passa como argumentos da linha de comando (espaço de pilha) para ':'.
Juliano
15

Também é útil para programas poliglotas:

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

Esta é agora tanto um shell-script executável e um programa JavaScript: o que significa ./filename.js, sh filename.jse node filename.jstodo o trabalho.

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


Alguma explicação, conforme solicitado:

  • Os scripts de shell são avaliados linha por linha; e o execcomando, quando executado, finaliza o shell e substitui seu processo pelo comando resultante. Isso significa que, para o shell, o programa fica assim:

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

  • ... e, como descrito acima, o primeiro comando na primeira linha é um no-op (traduz para : //, ou se você preferir citar as palavras ':' '//',. Observe que o //termo não possui significado especial aqui, como ocorre no 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 verdadeira essência do programa: é a execchamada que substitui o shell-script sendo invocado , com um processo Node.js invocado para avaliar o restante do script.

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

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

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

ELLIOTTCABLE
fonte
Olá, você pode explicar o que : //;e ~function(){}fazer? Obrigado:)
Stphane
11
@Stphane Adicionado um detalhamento! Quanto ao ~function(){}, isso é um pouco mais complicado. Há um par de outras respostas aqui que toque nele, embora nenhum deles realmente explicar isso a minha satisfação ... se nenhuma dessas perguntas explica bem o suficiente para você, sinta-se livre para publicá-la como uma questão aqui, eu vou estar prazer em responder em profundidade sobre uma nova pergunta.
ELLIOTTCABLE
11
Eu não prestei atenção node. Portanto, a parte da função é sobre javascript! Estou bem com o operador unário na frente do IIFE. Eu pensei que isso também era uma festa e, na verdade, realmente não entendi o significado da sua postagem. Eu estou bem agora, obrigado pelo seu tempo gasto adicionando "quebra"!
Stphane
~{ No problem. (= }
ELLIOTTCABLE
12

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ê pode originar a biblioteca ( . mylib.sh) e chamar as funções diretamente depois disso ( lib_function1 arg1 arg2) ou evitar sobrecarregar seu espaço de nome e chamar a biblioteca com um argumento de função (mylib.sh lib_function1 arg1 arg2 ).

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

#! / bin / bash

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

# funções de biblioteca "públicas"
lib_function1 () {
    : Esta função faz algo complicado com dois argumentos.
    :
    : Parâmetros:
    : 'arg1 - primeiro argumento ($ 1)'
    : 'arg2 - segundo argumento'
    :
    : Resultado:
    : " é complicado"

    # código de função real começa aqui
}

lib_function2 () {
    : Documentação de funções

    # código de função aqui
}

# help function
--Socorro() {
    eco MyLib v0.0.1
    eco
    eco Uso: mylib.sh [nome_da_função [args]]
    eco
    eco Funções disponíveis:
    declarar -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
    # invoca a função solicitada ou exibe ajuda
    if ["$ (digite -t ​​-" $ 1 "2> / dev / null)" = função]; 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. Somente estes devem ser invocados pelo usuário e listados no texto de ajuda.
  2. O recurso de auto-documentação baseia-se no ponto anterior e usa declare -fpara enumerar todas as funções disponíveis, depois as filtra através do sed para exibir apenas funções com o prefixo apropriado.
  3. É uma boa idéia colocar a documentação entre aspas simples, para evitar expansão indesejada e remoção de espaço em branco. Você também precisará ter cuidado ao usar apóstrofos / aspas no texto.
  4. Você pode escrever um código para internalizar o prefixo da biblioteca, ou seja, o usuário só precisa digitar mylib.sh function1e é traduzido internamente para lib_function1. Este é um exercício deixado para o leitor.
  5. A função de ajuda é denominada "--help". Essa é uma abordagem conveniente (preguiçosa) que usa o mecanismo de chamada da biblioteca para exibir a ajuda em si, sem a necessidade de codificar uma verificação extra $1. Ao mesmo tempo, o espaço para nome será confuso se você for fonte da biblioteca. Se você não gostar disso, poderá alterar o nome para algo como lib_helpou realmente verificar os argumentos --helpno código principal e chamar a função de ajuda manualmente.
Sir Athos
fonte
4

Vi esse uso em um script e pensei que era um bom substituto para invocar o nome de base em um script.

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

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

Griff Derryberry
fonte
Eu prefirobasetool=${0##*/}
Amit Naidu