A declaração if if é equivalente a lógica e && ou || e onde devo preferir um sobre o outro?

27

Estou aprendendo sobre estruturas de tomada de decisão e me deparei com estes 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 maneira. Existem vantagens em usar uma maneira da outra?

Subhaa Chandar
fonte
3
Eles não são equivalentes. Veja a excelente resposta de Ícaro. Por exemplo, considere o caso em que ./myfile existe mas não é legível.
AlexP

Respostas:

26

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

Com if A; then B; else C; fi, o comando Aé sempre avaliado e executado (pelo menos é feita uma tentativa de executá-lo) e, em seguida, um comando Bou comando Cé avaliado e executado.

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

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

Meu conselho: use A && Bou o A || Bque quiser, isso continua 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.

xhienne
fonte
29

A maioria das pessoas acham que é mais fácil compreender a if... then... else... fiformulário.

Para o a && b || c, você precisa ter certeza de que bretorna verdadeiro. Isso é uma causa de erros sutis e é uma boa razão para evitar esse estilo. Se b não retornar true, eles não serão os mesmos.

 $ 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 comprimento reduzido é 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 é conhecido mais testes desnecessários são ignorados. a && b || cestá agrupado como (a && b) || c. Primeiro aé executado. Se failsfor definido como não retornando um status de saída 0, o grupo (a && b)é conhecido faile bnão precisa ser executado. O ||não sabe o resultado da expressão, portanto, precisa executar c. Se afor bem-sucedido (retorna zero), o &&operador ainda não sabe o resultado, a && bportanto, precisa executar bpara descobrir. Se bfor a && bbem- sucedido, obtém êxito e ||sabe que o resultado geral é bem-sucedido, portanto, não precisa ser executado c. Se bfalhar, então||ainda não sabe o valor da expressão, é necessário executar c.

Icaro
fonte
7

O 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 retornar verdadeiro (êxito, código de saída 0), o comando B será executado. Se A falhar (retornará um código de saída falso diferente de 0) e / ou B falhar (retornar falso ), o comando C será executado.

Além disso, o &&operador é usado como um E nas verificações de condição e o operador ||trabalha como OU nas verificações de condição.

Dependendo do que você deseja fazer com seu script, o formulário A && B || Cpode ser usado para verificações de condições como o seu exemplo ou para encadear comandos e garantir que uma série de comandos seja executada se os comandos anteriores tiverem um código de saída 0 bem-sucedido .
É 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 peça echo "Something"será executada apenas se mkdir testfor bem-sucedida e a operação retornar 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.

Se você tentar implementar as "cadeias" acima com o if - then - else , precisará de muito mais comandos e verificações (e, portanto, mais código para escrever - mais coisas 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 diferem e 0 se não). Tais comandos precisam ser tratados com cuidado em && e || .

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

George Vasiliou
fonte
1

Grande parte da confusão sobre isso pode dever-se à documentação do bash que chama essas listas AND e OR . Embora sejam logicamente semelhantes aos &&e ||encontrados entre colchetes, eles funcionam de maneira diferente.

Alguns exemplos podem ilustrar isso melhor ...

NOTA: Os colchetes simples e duplos ( [ ... ]e [[ ... ]]) são comandos próprios que fazem uma comparação e retornam um código de saída. Eles realmente não precisam do if.

cmda  && cmdb  || cmdc

Se cmdasair verdadeiro, cmdbé executado.
Se cmdasair falso, cmdbNÃO é executado, mas cmdcé.

cmda; cmdb  && cmdc  || cmdd

Como cmdasaídas é ignorado.
Se cmdbsair verdadeiro, cmdcé executado.
Se cmdbsair falso, cmdcNÃO é executado e cmddé.

cmda  && cmdb; cmdc

Se cmdasai verdadeiro, cmdbé executado, seguido por cmdc.
Se cmdasair falso, cmdbNÃO é executado, mas cmdcé.

Hã? Por que é cmdcexecutado?
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, precisamos incluir cmdb; cmdcchaves entre chaves para torná-las um 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 cmdasai verdadeiro, cmdbé executado, seguido por cmdc.
Se cmdasaídas falsas, nem cmdbou cmdcé executado.
A execução continua com a próxima linha.

Uso

As listas de comandos condicionais são mais úteis para retornar o mais rápido possível das funções e, assim, evitar a interpretação e a execução de muitos códigos desnecessários. Entretanto, o retorno de várias funções significa que é preciso ser obsessivo em manter as funções curtas, para que seja 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
}
DocSalvager
fonte