Bash se [falso]; retorna verdadeiro

151

Estive aprendendo bash esta semana e se deparou com um problema.

#!/bin/sh

if [ false ]; then
    echo "True"
else
    echo "False"
fi

Isso sempre produzirá True, mesmo que a condição pareça indicar o contrário. Se eu remover os suportes [], ele funcionará, mas não entendo o porquê.

dez milhas
fonte
3
Aqui está algo para você começar.
Keyser
10
BTW, um script começando com #!/bin/shnão é um script bash - é um script sh POSIX. Mesmo que o interpretador POSIX sh no seu sistema seja fornecido pelo bash, ele desativa várias extensões. Se você deseja escrever scripts bash, use #!/bin/bashou seu equivalente localmente apropriado ( #!/usr/bin/env bashpara usar o primeiro interpretador bash no PATH).
Charles Duffy
Isso afeta [[ false ]]também.
CMCDragonkai

Respostas:

192

Você está executando o comando [(aka test) com o argumento "false", não executando o comando false. Como "false" é uma sequência não vazia, o testcomando sempre é bem-sucedido. Para realmente executar o comando, solte o [comando.

if false; then
   echo "True"
else
   echo "False"
fi
chepner
fonte
4
A idéia de falso ser um comando, ou mesmo uma string, me parece estranha, mas acho que funciona. Obrigado.
Tenmiles
31
bashnão possui um tipo de dados booleano e, portanto, nenhuma palavra-chave representando true e false. A ifinstrução apenas verifica se o comando que você fornece é bem-sucedido ou falha. O testcomando pega uma expressão e obtém êxito se a expressão for verdadeira; uma seqüência de caracteres não vazia é uma expressão avaliada como verdadeira, assim como na maioria das outras linguagens de programação. falseé um comando que sempre falha. (Por analogia, trueé um comando que sempre tem sucesso.)
chepner
2
if [[ false ]];retorna verdadeiro também. Como faz if [[ 0 ]];. E if [[ /usr/bin/false ]];também não pula o bloco. Como falso ou 0 pode ser avaliado como diferente de zero? Porra, isso é frustrante. Se isso importa, OS X 10.8; Bash 3.
jww 17/06/16
7
Não confunda falsecom uma constante booleana ou 0com um literal inteiro; ambos são apenas cordas comuns. [[ x ]]é equivalente a [[ -n x ]]qualquer string xque não comece com um hífen. bashnão tem quaisquer constantes booleanas em qualquer contexto. Seqüências de caracteres que se parecem com números inteiros são tratadas como tal por dentro (( ... ))ou por operadores como -eqou por -ltdentro [[ ... ]].
chepner
2
@ tbc0, existem mais preocupações do que apenas como algo se lê. Se variáveis ​​são definidas por código que não estão inteiramente sob seu controle (ou que podem ser manipuladas externamente, seja por nomes de arquivos incomuns ou outros), if $predicatepodem ser facilmente abusadas para executar código hostil de uma maneira que if [[ $predicate ]]não pode.
Charles Duffy
55

Primer Booleano Rápido para Bash

A ifdeclaração tem um comando como um argumento (como fazem &&, ||, etc.). O código de resultado inteiro do comando é interpretado como um booleano (0 / null = true, 1 / else = false).

A testinstrução aceita operadores e operandos como argumentos e retorna um código de resultado no mesmo formato que if. Um alias da testdeclaração é [, que é frequentemente usado ifpara realizar comparações mais complexas.

As instruções truee falsenão fazem nada e retornam um código de resultado (0 e 1, respectivamente). Portanto, eles podem ser usados ​​como literais booleanos no Bash. Mas se você colocar as instruções em um local em que elas sejam interpretadas como seqüências de caracteres, você terá problemas. No seu caso:

if [ foo ]; then ... # "if the string 'foo' is non-empty, return true"
if foo; then ...     # "if the command foo succeeds, return true"

Assim:

if [ true  ] ; then echo "This text will always appear." ; fi;
if [ false ] ; then echo "This text will always appear." ; fi;
if true      ; then echo "This text will always appear." ; fi;
if false     ; then echo "This text will never appear."  ; fi;

Isto é semelhante ao fazer algo como echo '$foo'vs. echo "$foo".

Ao usar a testinstrução, o resultado depende dos operadores utilizados.

if [ "$foo" = "$bar" ]   # true if the string values of $foo and $bar are equal
if [ "$foo" -eq "$bar" ] # true if the integer values of $foo and $bar are equal
if [ -f "$foo" ]         # true if $foo is a file that exists (by path)
if [ "$foo" ]            # true if $foo evaluates to a non-empty string
if foo                   # true if foo, as a command/subroutine,
                         # evaluates to true/success (returns 0 or null)

Em resumo , se você quiser apenas testar algo como aprovado / reprovado (também conhecido como "verdadeiro" / "falso"), passe um comando para sua declaração ifou &&etc., sem colchetes. Para comparações complexas, use colchetes com os operadores adequados.

E sim, eu estou ciente que não há tal coisa como um tipo boolean nativa em Bash, e que ife [e truesão tecnicamente "comandos" e não "declarações"; essa é apenas uma explicação funcional muito básica.

Beejor
fonte
1
Para sua informação, se você deseja usar 1/0 como Verdadeiro / Falso no seu código, isso if (( $x )); then echo "Hello"; fimostra a mensagem para x=1mas não para x=0ou x=(indefinida).
Jonathan H
3

Eu descobri que posso fazer alguma lógica básica executando algo como:

A=true
B=true
if ($A && $B); then
    C=true
else
    C=false
fi
echo $C
Rodrigo
fonte
6
Um possível , mas é uma péssima idéia. ( $A && $B )é uma operação surpreendentemente ineficiente - você gera um subprocesso por meio de chamada e fork(), em seguida, divide o conteúdo de $Apara gerar uma lista de elementos (neste caso, de tamanho um) e avalia cada elemento como um glob (neste caso, um que avalia por si próprio) e, em seguida, executa o resultado como um comando (nesse caso, um comando interno).
Charles Duffy
3
Além disso, se suas truee falsestrings vierem de algum outro lugar, existe o risco de elas realmente conterem conteúdo diferente de trueou falsee você poderá obter um comportamento inesperado, incluindo a execução arbitrária de comandos.
Charles Duffy
6
É muito, muito mais seguro escrever A=1; B=1e if (( A && B ))- tratá-los como numéricos - ou [[ $A && $B ]], que trata uma sequência vazia como falsa e uma sequência não vazia como verdadeira.
Charles Duffy
3
(observe que estou usando apenas os nomes das variáveis ​​all-caps acima para seguir as convenções estabelecidas pela resposta - o POSIX especifica explicitamente os nomes all-caps a serem usados ​​para variáveis ​​de ambiente e componentes internos do shell e reserva nomes em minúsculas para uso do aplicativo; para evitar conflitos com os ambientes futuros do sistema operacional, principalmente porque a configuração de uma variável do shell substitui qualquer variável de ambiente com o mesmo nome, é sempre ideal honrar essa convenção nos próprios scripts).
Charles Duffy
Obrigado pelas ideias!
197 Rodrigo
2

Usar true / false remove algumas desordens de colchetes ...

#! /bin/bash    
#  true_or_false.bash

[ "$(basename $0)" == "bash" ] && sourced=true || sourced=false

$sourced && echo "SOURCED"
$sourced || echo "CALLED"

# Just an alternate way:
! $sourced  &&  echo "CALLED " ||  echo "SOURCED"

$sourced && return || exit
psh
fonte
Achei isso útil, mas no ubuntu 18.04 $ 0 era "-bash" e o comando basename gerou um erro. Alterar esse teste para que [[ "$0" =~ "bash" ]] o script funcione para mim.
WiringHarness