São if outra instrução equivalente a lógica e && ou || e onde devo preferir um sobre o outro?

25

Estou aprendendo sobre estruturas de tomada de decisão e me deparei com esses códigos:

if [ -f ./myfile ]
then
     cat ./myfile
else
     cat /home/user/myfile
fi


[ -f ./myfile ] &&
cat ./myfile ||
cat /home/user/myfile

Ambos se comportam da mesma forma. Há alguma vantagem em usar um caminho do outro?

    
por Subhaa Chandar 08.01.2017 / 21:04

4 respostas

23

Não, as construções if A; then B; else C; fi e A && B || C são não equivalentes .

Com if A; then B; else C; fi , o comando A é sempre avaliado e executado (pelo menos uma tentativa de execução é feita) e, em seguida, o comando B ou o comando C é avaliado e executado.

Com A && B || C , é o mesmo para os comandos A e B , mas diferente para C : o comando C é avaliado e executado se ou A falhar ou B falha.

No seu exemplo, suponha que você chmod u-r ./myfile , apesar de [ -f ./myfile ] ser bem-sucedido, você irá cat /home/user/myfile

Meu conselho: use A && B ou A || B o que você quiser, isso fica fácil de ler e entender e não há armadilha. Mas se você quer dizer se ... então ... mais ... então use if A; then B; else C; fi .

    
por 08.01.2017 / 21:37
27

A maioria das pessoas acha mais fácil compreender o if ... then ... else ... fi form.

Para o a && b || c , você precisa ter certeza de que b retorna verdadeiro. Esta é uma causa de bugs sutis e é uma boa razão para evitar esse estilo. Se b não retornar verdadeiro, não é o mesmo.

 $ if true; then false ; else echo boom ; fi
 $ true && false || echo boom
 boom

Para testes e ações muito curtos que não possuem uma cláusula else, o tamanho abreviado é atraente, por exemplo

 die(){ printf "%s: %s\n" "$0" "$*" >&2 ; exit 1; }

 [ "$#" -eq 2] || die "Needs 2 arguments, input and output"

 if [ "$#" -ne 2 ] ; then
     die "Needs 2 arguments, input and output"
 fi

&& e || são short circuiting operators , assim que o resultado for conhecido, testes adicionais desnecessários serão ignorados. a && b || c está agrupado como (a && b) || c . Primeiro a é executado. Se fails for definido como não retornando um status de saída igual a 0, o grupo (a && b) será conhecido como fail e b não precisará ser executado. O || não conhece o resultado da expressão, portanto, precisa executar c . Se a for bem-sucedido (retorna zero), o operador && ainda não sabe o resultado de a && b , por isso precisa executar b para descobrir. Se b for bem-sucedido, então a && b será bem-sucedido e o || saberá que o resultado geral é bem-sucedido, portanto, não precisa executar c . Se b falhar, então || ainda não sabe o valor da expressão, portanto, precisa executar c .

    
por 08.01.2017 / 21:35
7

Operador & & executa o próximo comando se o comando anterior teve uma execução bem-sucedida, (código de saída retornado ($?) 0 = true lógico).

No formulário A && B || C , o comando (ou condição) A é avaliado e se A retorna verdadeiro (sucesso, código de saída 0) então o comando B é executado. Se A falhar (assim retornará false - código de saída diferente de 0) e / ou B falhará (retornando false ) então o comando C será executado.

O operador && também é usado como AND nas verificações de condição e o operador || funciona como OR nas verificações de condição.

Dependendo do que você deseja fazer com seu script, o formulário A && B || C pode ser usado para verificações de condição como seu exemplo ou pode ser usado para encadear comandos e garantir que uma série de comandos seja executada se os comandos anteriores tiverem uma saída bem-sucedida código 0 .
É por isso que é comum ver comandos como: do_something && do_something_else_that_depended_on_something .

Exemplos: apt-get update && apt-get upgrade Se a atualização falhar, a atualização não será executada (faz sentido no mundo real ...).

mkdir test && echo "Something" > test/file
A parte echo "Something" será executada apenas se mkdir test tiver êxito e a operação tiver retornado o código de saída 0 .

./configure --prefix=/usr && make && sudo make install
Geralmente encontrado na compilação de tarefas para encadear comandos dependentes necessários juntos.

Se você tentar implementar "chains" acima com if - então - else você precisará de muito mais comandos e verificações (e assim mais código para escrever - mais coisas para dar errado) para uma tarefa simples.

Além disso, lembre-se de que comandos encadeados com & & e || são lidos pelo shell da esquerda para a direita. Pode ser necessário agrupar comandos e verificações de condição com colchetes para depender da próxima etapa da saída bem-sucedida de alguns comandos anteriores. Por exemplo, veja isto:

root@debian:$ true || true && false;echo $?
1 
#read from left to right
#true OR true=true AND false = false = exit code 1=not success

root@debian:$ true || (true && false);echo $?
0 
# true OR (true AND false)=true OR false = true = exit code 0 = success

Ou um exemplo da vida real:

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 ]] && [[ $c -eq 2 ]];echo $?
1 
#condition $a = true OR condition b = true AND condition $c = false
#=> yields false as read from left to right, thus exit code=1 = not ok

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 && $c -eq 2 ]];echo $?
0 
#vars b and c are checked in a group which returns false, 
#condition check of var a returns true, thus true OR false yields true = exit code 0

Lembre-se de que alguns comandos retornam códigos de saída diferentes dependendo do processo executado ou retornam códigos diferentes dependendo de suas ações (por exemplo, comando GNU diff , retorna 1 se dois arquivos diferirem e 0 se não o fizerem). Tais comandos precisam ser tratados com cuidado em & & e || .

Além disso, apenas para ter todo o quebra-cabeça, lembre-se da concatenação de comandos usando o operador ; . Com um formato A;B;C , todos os comandos serão executados em série, independentemente do código de saída do comando A e B .

    
por 08.01.2017 / 23:58
2

Grande parte da confusão sobre isso pode ser devido à documentação bash que chama esses AND e OU lista . Embora sejam logicamente semelhantes aos && e || encontrados entre colchetes, eles funcionam de maneira diferente.

Alguns exemplos podem ilustrar isso melhor ...

NOTE: Single and double square brackets ([ ... ] and [[ ... ]]) are commands in their own right that do a comparison and return an exit code. They do not actually need the if.

cmda  && cmdb  || cmdc

Se cmda sair verdadeiro, cmdb será executado.
Se cmda sair falso, cmdb NÃO será executado, mas cmdc será. / em>

cmda; cmdb  && cmdc  || cmdd

Como cmda exits é ignorado.
Se cmdb sair true, cmdc será executado.
Se cmdb exits false, cmdc NÃO é executado e cmdd é.

cmda  && cmdb; cmdc

Se cmda sair true, cmdb for executado, seguido por cmdc .
Se cmda sair falso, cmdb NÃO será executado mas cmdc é.

Huh? Por que cmdc é executado?
Porque para o intérprete, um ponto e vírgula ( ; ) e uma nova linha significam exatamente a mesma coisa. Bash vê essa linha de código como ...

cmda  && cmdb
cmdc  

Para alcançar o que é esperado, devemos colocar cmdb; cmdc dentro das chaves para torná-las Comando Composto (comando de grupo) . O ponto-e-vírgula de terminação adicional é apenas um requisito da sintaxe { ...; } . Então nós temos ...

cmda && { cmdb; cmdc; }
Se cmda sair verdadeiro, cmdb será executado, seguido por cmdc .
Se cmda sair falso,% nãocmdb ou cmdc é executado.
A execução continua com a próxima linha.

Uso

Listas de comandos condicionais são mais úteis para retornar o mais rápido possível de funções e, assim, evitar interpretar e executar muitos códigos desnecessários. No entanto, retornos de múltiplas funções significam que é preciso ser obsessivo em manter as funções curtas, por isso é mais fácil garantir que todas as condições possíveis sejam cobertas.

Aqui está um exemplo de algum código em execução ...

fnInit () {
  :
  _fn="$1"
  ### fnInit "${FUNCNAME}" ...
  ### first argument MUST be name of the calling function
  #
  [[ "$2" == "--help-all" ]]  && { helpAll                      ; return 0; }
  ### pick from list of functions
  #
  [[ "$2" == "--note-all" ]]  && { noteAll                      ; return 0; }
  ### pick from notes in METAFILE
  #
  [[ "$2" == "--version"  ]]  && { versionShow "${_fn}" "${@:3}"; return 0; }
  #
  [[ "$2" == "--function" ]]  && {
    isFnLoaded "$3"           && { "${@:3}"                     ; return 0; }
    #
    errorShow functionnotfound "Unknown function:  $3"
    return 0
  }
  ### call any loaded function
  #
  [[ "$2" == "--help" || "$2" == "-h" ]]  && { noteShow "$_fn" "${@:3}"; return 0; }
  ### fnInit "${FUNCNAME}" --help or -h
  #
  return 1
}
    
por 13.01.2017 / 03:19