O que significa "teste $ 2 &&" neste script bash?

7

Estou vendo um script bash com o seguinte código:

#!/bin/sh

set -e # Exit if any command fails

# If multiple args given run this script once for each arg
test $2 && {
  for arg in $@
    do $0 $arg
  done
  exit
}
.
.
.

Como mencionado no comentário, o objetivo é "executar o script em cada argumento, se houver mais de um" Eu tenho um código de execução para testar isso:

# install-unit
# If multiple args given run this script once for each arg
test $2 && {
  echo "\
sh ❯ ./multiple-run.sh cats and dogs and stuff
$0: ./multiple-run.sh
$1: cats
$2: and
test $2 && is true
running: cats
running: and
running: dogs
running: and
running: stuff
sh ❯ ./multiple-run.sh cats
running: cats
: $0" echo "\: $1" echo "\: $2" echo "test \ && is true" } test $2 && { # note this is just a regular bash for loop # http://goo.gl/ZHpBvT for arg in $@ do $0 $arg done exit } echo "running: $1"

que fornece o seguinte:

echo "positional parameters"
for arg do
  echo "$arg"
done

echo "\nin keyword"
for arg in "$@"
  do echo "$arg"
done

Eu gostaria que alguém desempacotasse a parte test $2 && {} e o shell exit embutido. No começo eu estava pensando que o test $2 estava verificando se há mais argumentos, e talvez seja , mas parece estranho porque parece que há duas expressões separadas por "e" && , e eu também estou apenas confuso com o que diabos o exit builtin faz.

Explicações simples em inglês com exemplos e referências de documentação são apreciadas:)

Obrigado!

Edit: Obrigado Stéphane Chazelas por isso. Para quem está confuso com a mudança na sintaxe do loop, confira o número 3 neste geekstuff artigo. Para resumir:

❯ ./for-loop-positional.sh cats and dogs          
positional parameters
cats
and
dogs

in keyword
cats
and
dogs

nos dá:

#!/bin/sh

set -e # Exit if any command fails

# If multiple args given run this script once for each arg
test $2 && {
  for arg in $@
    do $0 $arg
  done
  exit
}
.
.
.
    
por mbigras 04.09.2016 / 06:20

2 respostas

14

Essa é uma maneira incorreta de escrever:

#!/bin/sh -

set -e # Exit if any command fails

# If multiple args given run this script once for each arg
if [ "$#" -gt 1 ]; then
  for arg do
    "$0" "$arg"
  done
  exit
fi

Acho que o que o autor pretendia fazer era verificar se o segundo argumento era uma string não vazia (o que não é a mesma coisa que verificar se há mais de 1 argumento, já que o segundo argumento poderia ser passado, mas seria o corda vazia).

test somestring

Uma forma abreviada de

test -n somestring

Retorna true se somestring não for a string vazia. ( test '' retorna falso, test anythingelse retorna verdadeiro (mas cuidado com test -t em alguns shells que verificam se stdout é um terminal)).

No entanto, o autor esqueceu as aspas em torno da variável (em todas as variáveis, na verdade). O que isso significa é que o conteúdo de $2 está sujeito ao operador split + glob. Portanto, se $2 contiver caracteres de $IFS (espaço, guia e nova linha por padrão) ou caracteres glob ( * , ? , [...] ), isso não funcionará corretamente.

E se $2 estiver vazio (como quando menos de dois argumentos forem passados ou o segundo argumento estiver vazio), test $2 se tornará test , não test '' . test não recebe nenhum argumento (vazio ou não).

Felizmente, nesse caso, test sem argumentos retorna false. É um pouco melhor que test -n $2 , o que teria retornado true (já que se tornaria test -n , igual a test -n -n ), de modo que o código pareceria funcionar em alguns casos.

Para resumir:

  • para testar se 2 ou mais argumentos são passados:

    [ "$#" -gt 1 ]
    

    ou

    [ "$#" -ge 2 ]
    
  • para testar se uma variável não está vazia:

    [ -n "$var" ]
    [ "$var" != '' ]
    [ "$var" ]
    

    todos os quais são confiáveis em implementações POSIX de [ , mas se você tiver que lidar com sistemas muito antigos, você pode ter que usar

    [ '' != "$var" ]
    

    em vez de implementações de [ que se sufocam em valores de $var como = , -t , ( ...

  • para testar se uma variável está definida (que pode ser usada para testar se o script recebe um segundo argumento, mas usar $# é muito mais fácil de ler e mais idiomático):

    [ "${var+defined}" = defined ]
    

(ou o equivalente com a forma test . Usar o alias [ para o comando test é mais comum).

Agora, a diferença entre cmd1 && cmd2 e if cmd1; then cmd2; fi .

Ambos executam cmd2 apenas se cmd1 for bem-sucedido. A diferença nesse caso é que o status de saída da lista geral de comandos será o do último comando executado no caso && (portanto, um código falha se cmd1 não for return true (embora isso não atropele set -e aqui)) enquanto no caso if , isso será o de cmd2 ou 0 (sucesso) se cmd2 não for executado.

Portanto, nos casos em que cmd1 deve ser usado como uma condição (quando sua falha não deve ser considerada como um problema ), geralmente é melhor usar if , especialmente se for a última coisa que você faz em um script, pois isso definirá o status de saída do seu script. Também faz um código mais legível.

A forma cmd1 && cmd2 é mais comumente usada como condições , como em:

if cmd1 && cmd2; then...
while cmd1 && cmd2; do...

Isso ocorre em contextos em que nos preocupamos com o status de saída desses dois comandos.

    
por 04.09.2016 / 06:39
5

test $2 && some_command é composto por dois comandos:

  • test $2 está verificando se a string após a expansão do segundo argumento ( $2 ) tem comprimento diferente de zero, ou seja, está necessariamente verificando test -n $2 ( [ -n $2 ] ). Observe que, como você não usou aspas em torno de $2 , test será bloqueado em valores com espaços em branco. Você deve usar test "$2" aqui.

  • && é um operador de avaliação de curto-circuito, que indica que o comando após && será executado somente se o comando for bem-sucedido, ou seja, tiver o código de saída 0. No exemplo acima, some_command só será executado se test $2 for bem-sucedida, ou seja, se a cadeia tiver comprimento diferente de zero, então some_command será executado.

por 04.09.2016 / 06:28