Qual é o caso de uso de noop [:] no bash?


123

Procurei por noop no bash (:), mas não consegui encontrar nenhuma informação boa. Qual é o objetivo exato ou o caso de uso desse operador?

Tentei seguir e está funcionando assim para mim:

[mandy@root]$ a=11
[mandy@root]$ b=20
[mandy@root]$ c=30
[mandy@root]$ echo $a; : echo $b ; echo $c
10
30

Informe-me sobre qualquer caso de uso desse operador em tempo real ou qualquer local onde seja obrigatório usá-lo.



Observe que o :built-in existe no bourne shell e no ksh, bem como no bash.
ghoti 13/09/12


6
Como isso não é uma pergunta real? Eu acho que é uma pergunta muito boa. Eu até tenho um bom uso, mas não posso postar uma resposta.
Steven Lu

Respostas:


175

Está lá mais por razões históricas. O cólon embutido :é exatamente equivalente a true. É tradicional usar truequando o valor de retorno é importante, por exemplo, em um loop infinito:

while true; do
  echo 'Going on forever'
done

É tradicional usar :quando a sintaxe do shell requer um comando, mas você não tem nada a fazer.

while keep_waiting; do
  : # busy-wait
done

O :builtin remonta ao shell Thompson , estava presente no Unix v6 . :foi um indicador de etiqueta para a gotodeclaração do shell Thompson . O rótulo pode ser qualquer texto, então :dobrado como um indicador de comentário (se não houver goto comment, : commenté efetivamente um comentário). A concha Bourne não teve, gotomas manteve :.

Um idioma comum usado :é o : ${var=VALUE}que define varcomo VALUEse não estava vardefinido e não faz nada se já estiver definido. Essa construção existe apenas na forma de uma substituição de variável, e essa substituição de variável precisa fazer parte de um comando de alguma maneira: um comando no-op serve muito bem.

Veja também Que finalidade serve o cólon incorporado? .


11
Bom resumo. Além disso, o shell Bourne original não era usado #para comentários (ou #!/bin/shshebangs); o :comando introduziu comentários e ai do programador ingênuo que fez uma bela caixa de estrelas em seus comentários: : ****(ou, pior, : * * *).
31812 Jonathan Leffler

3
@ JonathanLeffler: Que vergonha para mim, mas eu não entendo. O que aconteceria com : * * *?
DevSolar

7
Por :ser um comando, o shell ainda precisa processar seus argumentos antes de descobrir que os :ignora. Principalmente, você está apenas fazendo com que o shell faça um trabalho extra ao expandir *para uma lista de arquivos no diretório atual; na verdade, não afetará como o script funciona.
chepner

Suponho que ele pode fazer com que seu script falhe se você for executado em um diretório com muitos arquivos, fazendo a expansão glob ultrapassar o limite de tamanho do comando? Talvez apenas se você tiver set -e. De qualquer forma, espero que todos nós podemos apenas concordar em usar #:)
Jack O'Connor

2
Às vezes uso while : ; do ... ; donese quero um loop infinito rápido e sujo. (Normalmente há uma sleepno circuito, e eu digite Ctrl-C para matá-lo.)
Keith Thompson

21

Eu o uso para instruções if quando eu comento todo o código. Por exemplo, você tem um teste:

if [ "$foo" != "1" ]
then
    echo Success
fi

mas você deseja comentar temporariamente tudo o que está contido em:

if [ "$foo" != "1" ]
then
    #echo Success
fi

O que faz com que o bash dê um erro de sintaxe:

line 4: syntax error near unexpected token `fi'
line 4: `fi'

O Bash não pode ter blocos vazios (WTF). Então você adiciona um no-op:

if [ "$foo" != "1" ]
then
    #echo Success
    :
fi

ou você pode usar o no-op para comentar as linhas:

if [ "$foo" != "1" ]
then
    : echo Success
fi

12

Se você usar set- e, || :é uma ótima maneira de não sair do script se ocorrer uma falha (isso explicitamente faz com que seja aprovado).


1
Eu achei isso útil para certos aplicativos de chamada de script de shell. Por exemplo, o Automator no Mac sai por erro e não informa o porquê. Mas || :, depois, você mesmo manipular o erro com um exit 0comando permitirá exibir todos os stdout e stderr na janela do aplicativo durante a depuração. Considere o { foo ... 2>&1; } || :que é muito mais simples do que configurar traps e if-thens mais complexos.
Beejor

7

Você usaria :para fornecer um comando que tenha êxito, mas não faz nada. Neste exemplo, o comando "verbosidade" é desativado por padrão, configurando-o para :. A opção 'v' ativa.

#!/bin/sh
# example
verbosity=:                         
while getopts v OPT ; do          
   case $OPT in                  
       v)        
           verbosity=/bin/realpath 
       ;;
       *)
           exit "Cancelled"
       ;;             
   esac                          
done                              

# `$verbosity` always succeeds by default, but does nothing.                              
for i in * ; do                   
  echo $i $($verbosity $i)         
done                              

$ example
   file

$ example -v
   file /home/me/file  

estritamente falando, atribuir um valor a uma variável é "fazer alguma coisa", portanto, por que ela não gera um erro na sua declaração de caso.
Qodeninja 20/07

@qodeninja: Ninguém afirmou que a atribuição de variável "não fez nada"; o ponto é que o $(verbosity $i)posterior (e condicionalmente) não faz nada.
Lightness Races em órbita

6

Ignorar aliasargumentos

Algumas vezes você deseja ter um alias que não leva em consideração nenhum argumento. Você pode fazer isso usando ::

> alias alert_with_args='echo hello there'

> alias alert='echo hello there;:'

> alert_with_args blabla
hello there blabla

> alert blabla
hello there

não é o downvoter, mas esta resposta não demonstra como :funciona, infelizmente. O exemplo que você forneceu parece redundante.
Qodeninja 20/07

2
A questão não é como funciona, mas qual é o caso de uso . É verdade que minha resposta é um pouco semelhante a stackoverflow.com/a/37170755/6320039 . Mas não é realmente o mesmo caso de uso. Eu ficaria feliz em melhorar a minha resposta, mas eu realmente não krnow como aqui ..
Ulysse BN

1
Isso é um pouco desagradável - uma caixa de canto, se você preferir -, mas ainda bem interessante e pode ser um truque inteligente de se ter no bolso de trás.
Mike S

2
O mesmo pode ser alcançado com #
Zoey Hewll

2
@ZoeyHewll, não exatamente. Como os aliases são substituídos textualmente antes de qualquer tipo de execução, o uso de um alias para #fazer com que tudo o que vem sintaticamente depois da mesma linha seja ignorado. Isso é realmente diferente (considere my_alias arg ; other_commandou, inversamente my_alias arg1 {BACKSLASH}{NEWLINE} arg2) e provavelmente não é o que você deseja, pois pode causar erros de sintaxe (adivinhe o que while my_alias ; do true ; doneou o que while true ; do my_alias ; donefará).
Maëlan

4

Um uso é como comentários de várias linhas ou para comentar parte de seu código para fins de teste, usando-o em conjunto com um arquivo aqui.

: << 'EOF'

This part of the script is a commented out

EOF

Não se esqueça de usar aspas EOFpara que nenhum código interno seja avaliado, como$(foo) . Ele também pode valer a pena usar um nome terminator intuitiva como NOTES, SCRATCHPADou TODO.


Truque legal! Especialmente para notas e códigos de rascunho que exigem dezenas de "#" s quando embrulhados em papel. Um efeito colateral é que, diferentemente das linhas de comentário, alguns marcadores de sintaxe ignoram os heredocs, colorindo-os como código normal (ou pelo menos texto sem formatação). O que pode ser ótimo para examinar o código comentado ou salvar os olhos dos parágrafos em uma cor de comentário neon. Apenas uma ressalva é que esses blocos devem ser anotados como comentários em código compartilhado, pois a sintaxe é única e pode causar confusão. Então, novamente, é sobre isso que estamos falando, onde o potencial de confusão é sistêmico.
Beejor

3

Dois meus.

Incorporar comentários de POD

Uma aplicação bastante descolada :é para incorporar comentários POD em scripts bash , para que as páginas de manual possam ser geradas rapidamente. Obviamente, alguém poderia reescrever o script inteiro em Perl ;-)

Ligação de Função em Tempo de Execução

Esse é um tipo de padrão de código para funções de ligação em tempo de execução. Fi, tenha uma função de depuração para fazer algo apenas se um determinado sinalizador estiver definido:

#!/bin/bash
# noop-demo.sh 
shopt -s expand_aliases

dbg=${DBG:-''}

function _log_dbg {
    echo >&2 "[DBG] $@"
}

log_dbg_hook=':'

[ "$dbg" ] && log_dbg_hook='_log_dbg'

alias log_dbg=$log_dbg_hook


echo "Testing noop alias..."
log_dbg 'foo' 'bar'

Você obtém:

$ ./noop-demo.sh 
Testing noop alias...
$ DBG=1 ./noop-demo.sh 
Testing noop alias...
[DBG] foo bar

2

Às vezes, cláusulas não operacionais podem tornar seu código mais legível.

Isso pode ser uma questão de opinião, mas aqui está um exemplo. Vamos supor que você criou uma função que funciona seguindo dois caminhos unix. Ele calcula o 'caminho de mudança' necessário para cd de um caminho para outro. Você coloca uma restrição em sua função de que os caminhos devem começar com '/' OU os dois não.

function chgpath() {
    # toC, fromC are the first characters of the argument paths.
    if [[ "$toC" == / && "$fromC" == / ]] || [[ "$toC" != / && "$fromC" != / ]]
    then
        true      # continue with function
    else
        return 1  # Skip function.
    fi

Alguns desenvolvedores desejam remover o no-op, mas isso significaria negar o condicional:

function chgpath() {
    # toC, fromC are the first characters of the argument paths.
    if [[ "$toC" != / || "$fromC" == / ]] && [[ "$toC" == / || "$fromC" != / ]]
    then
        return 1  # Skip function.
    fi

Agora - na minha opinião - não está tão claro na cláusula se as condições nas quais você deseja pular a função. Para eliminar o no-op e fazê-lo claramente, convém mover a cláusula if da função:

    if [[ "$toC" == / && "$fromC" == / ]] || [[ "$toC" != / && "$fromC" != / ]]
    then
        cdPath=$(chgPath pathA pathB)   # (we moved the conditional outside)

Parece melhor, mas muitas vezes não podemos fazer isso; queremos que a verificação seja feita dentro da função.

Então, quantas vezes isso acontece? Não muito frequentemente. Talvez uma ou duas vezes por ano. Isso acontece com bastante frequência, para que você esteja ciente disso. Não evito usá-lo quando acho que melhora a legibilidade do meu código (independentemente do idioma).


3
Se você estiver respondendo a uma pergunta sobre o objetivo de :, deve usá-lo :, não truena resposta. Dito isto, a maneira mais fácil para negar a condicional aqui é usar um [[ ... ]]comando e prefixo com !: if ! [[ ( ... && ... ) || ( ... && ... ) ]]; then.
chepner

1
Ah, muito bom, e eu deveria votar na minha resposta. Hmmm .... ainda estou pensando em exemplos em que tive que usar uma cláusula de não operação. Este é o melhor que eu criei.
Bitdiot

É realmente apenas mantido para compatibilidade com versões anteriores, embora : ${...=...}seja indiscutivelmente menos estranho do que true ${...=...}. Alguns podem preferir também while :; doa while true; doconcisão.
chepner

2

Um pouco relacionado a esta resposta , acho este no-op bastante conveniente para hackear scripts poliglotas . Por exemplo, aqui está um comentário válido para o bash e o vimscript:

":" #    this is a comment
":" #    in bash, ‘:’ is a no-op and ‘#’ starts a comment line
":" #    in vimscript, ‘"’ starts a comment line

Certamente, podemos ter usado trueda mesma forma, mas :ser um sinal de pontuação e não uma palavra irrelevante em inglês deixa claro que é um símbolo de sintaxe.


Por que alguém faria algo tão complicado quanto escrever um script poliglota (além de legal): isso é útil em situações em que normalmente escrevíamos vários arquivos de script em vários idiomas diferentes, com arquivo Xreferente ao arquivo Y.

Em tal situação, a combinação dos dois scripts em um único arquivo poliglota evita qualquer trabalho Xpara determinar o caminho para Y(é simplesmente "$0"). Mais importante, torna mais conveniente mover ou distribuir o programa.

  • Um exemplo comum. Há um problema conhecido de longa data com shebangs: a maioria dos sistemas (incluindo Linux e Cygwin) permite que apenas um argumento seja passado ao intérprete. O seguinte shebang:

    #!/usr/bin/env interpreter --load-libA --load-libB

    disparará o seguinte comando:

    /usr/bin/env "interpreter --load-libA --load-libB" "/path/to/script"

    e não o pretendido:

    /usr/bin/env interpreter --load-libA --load-libB "/path/to/script"

    Assim, você acabaria escrevendo um script de wrapper, como:

    #!/usr/bin/env sh
    /usr/bin/env interpreter --load-libA --load-libB "/path/to/script"

    É aqui que a poliglossia entra em cena.

  • Um exemplo mais específico. Certa vez, escrevi um script bash que, entre outras coisas, invocava o Vim. Eu precisava dar Vim configuração adicional, o que poderia ser feito com a opção --cmd "arbitrary vimscript command here". No entanto, essa configuração foi substancial, de modo que incluir uma string seria terrível (se possível). Portanto, uma solução melhor era escrevê-lo em extenso em algum arquivo de configuração e fazer o Vim ler esse arquivo com -S "/path/to/file". Portanto, acabei com um arquivo poliglota bash / vimscript.


1

Eu também usei nele scripts para definir variáveis ​​padrão.


: ${VARIABLE1:=my_default_value}
: ${VARIABLE2:=other_default_value}
call-my-script ${VARIABLE1} ${VARIABLE2}

0

suponha que você tenha um comando que deseja associar ao sucesso de outro:

cmd="some command..."
$cmd
[ $? -eq 0 ] && some-other-command

mas agora você deseja executar os comandos condicionalmente e deseja mostrar os comandos que seriam executados (execução a seco):

cmd="some command..."
[ ! -z "$DEBUG" ] && echo $cmd
[ -z "$NOEXEC" ] && $cmd
[ $? -eq 0 ] && {
    cmd="some-other-command"
    [ ! -z "$DEBUG" ] && echo $cmd
    [ -z "$NOEXEC" ] && $cmd
}

portanto, se você definir DEBUG e NOEXEC, o segundo comando nunca será exibido. isso ocorre porque o primeiro comando nunca é executado (porque NOEXEC não está vazio), mas a avaliação desse fato deixa você com um retorno de 1, o que significa que o comando subordinado nunca é executado (mas você deseja porque é uma execução a seco). Portanto, para corrigir isso, você pode redefinir o valor de saída deixado na pilha com um noop:

[ -z "$NOEXEC" ] && $cmd || :
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.