Bash si [faux]; renvoie vrai

151

J'ai appris à bash cette semaine et j'ai rencontré un hic.

#!/bin/sh

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

Cela produira toujours True même si la condition semble indiquer le contraire. Si je supprime les crochets, []cela fonctionne, mais je ne comprends pas pourquoi.

dix milles
la source
3
Voici quelque chose pour vous aider à démarrer.
keyer
10
BTW, un script commençant par #!/bin/shn'est pas un script bash - c'est un script POSIX sh. Même si l'interpréteur POSIX sh sur votre système est fourni par bash, il désactive un tas d'extensions. Si vous souhaitez écrire des scripts bash, utilisez #!/bin/bashou son équivalent localement approprié ( #!/usr/bin/env bashpour utiliser le premier interpréteur bash dans le PATH).
Charles Duffy
Cela affecte [[ false ]]aussi.
CMCDragonkai

Réponses:

192

Vous exécutez la commande [(aka test) avec l'argument "false", sans exécuter la commande false. Puisque "false" est une chaîne non vide, la testcommande réussit toujours. Pour exécuter réellement la commande, supprimez la [commande.

if false; then
   echo "True"
else
   echo "False"
fi
chepner
la source
4
L'idée que false soit une commande, voire une chaîne, me semble étrange, mais je suppose que cela fonctionne. Merci.
tenmiles
31
bashn'a pas de type de données booléen, et donc aucun mot-clé représentant vrai et faux. L' ifinstruction vérifie simplement si la commande que vous lui donnez réussit ou échoue. La testcommande prend une expression et réussit si l'expression est vraie; une chaîne non vide est une expression évaluée comme vraie, comme dans la plupart des autres langages de programmation. falseest une commande qui échoue toujours. (Par analogie, trueest une commande qui réussit toujours.)
chepner
2
if [[ false ]];renvoie vrai aussi. Comme le fait if [[ 0 ]];. Et if [[ /usr/bin/false ]];ne saute pas non plus le bloc. Comment false ou 0 peut-il être non nul? Merde, c'est frustrant. Si cela compte, OS X 10.8; Bash 3.
jww
7
Ne vous méprenez pas falsepour une constante booléenne ou 0pour un littéral entier; les deux ne sont que des chaînes ordinaires. [[ x ]]équivaut à [[ -n x ]]pour toute chaîne xqui ne commence pas par un trait d'union. bashn'a pas de constantes booléennes dans aucun contexte. Les chaînes qui ressemblent à des entiers sont traitées comme telles à l'intérieur (( ... ))ou avec des opérateurs tels que -eqou à l' -ltintérieur [[ ... ]].
chepner
2
@ tbc0, il y a plus de soucis à portée de main que la simple lecture de quelque chose. Si les variables sont définies par du code qui n'est pas entièrement sous votre contrôle (ou qui peut être manipulé en externe, que ce soit par des noms de fichiers inhabituels ou autrement), il if $predicatepeut facilement être abusé pour exécuter du code hostile d'une manière qui if [[ $predicate ]]ne le peut pas.
Charles Duffy
55

Un aperçu booléen rapide pour Bash

La ifdéclaration prend une commande comme argument (comme le font &&, ||etc.). Le code de résultat entier de la commande est interprété comme un booléen (0 / null = true, 1 / else = false).

L' testinstruction prend des opérateurs et des opérandes comme arguments et renvoie un code de résultat au même format que if. Un alias de l' testinstruction est [, qui est souvent utilisé avec ifpour effectuer des comparaisons plus complexes.

Les instructions trueet falsene font rien et renvoient un code de résultat (0 et 1, respectivement). Ils peuvent donc être utilisés comme littéraux booléens dans Bash. Mais si vous placez les instructions dans un endroit où elles sont interprétées comme des chaînes, vous rencontrerez des problèmes. Dans ton cas:

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

Alors:

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;

Ceci est similaire à faire quelque chose comme echo '$foo'contre echo "$foo".

Lors de l'utilisation de l' testinstruction, le résultat dépend des opérateurs utilisés.

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)

En bref , si vous voulez juste tester quelque chose comme réussite / échec (aka "vrai" / "faux"), alors passez une commande à votre instruction ifou &&etc., sans crochets. Pour des comparaisons complexes, utilisez des parenthèses avec les opérateurs appropriés.

Et oui, je suis conscient qu'il n'y a pas de type booléen natif dans Bash, et que ifet [et truesont techniquement des "commandes" et non des "instructions"; ce n'est qu'une explication fonctionnelle très basique.

Beejor
la source
1
Pour info, si vous souhaitez utiliser 1/0 comme Vrai / Faux dans votre code, cela if (( $x )); then echo "Hello"; fiaffiche le message pour x=1mais pas pour x=0ou x=(non défini).
Jonathan H
3

J'ai trouvé que je peux faire une logique de base en exécutant quelque chose comme:

A=true
B=true
if ($A && $B); then
    C=true
else
    C=false
fi
echo $C
Rodrigo
la source
6
On peut , mais c'est une très mauvaise idée. ( $A && $B )est une opération étonnamment inefficace - vous créez un sous-processus en appelant fork(), puis en divisant par chaîne le contenu de $Apour générer une liste d'éléments (dans ce cas de taille un), et en évaluant chaque élément comme un glob (dans ce cas, un qui s'évalue par lui-même), puis exécutez le résultat sous forme de commande (dans ce cas, une commande intégrée).
Charles Duffy
3
De plus, si vos chaînes trueet falseproviennent d'ailleurs, il y a un risque qu'elles contiennent en fait des contenus autres que trueou false, et vous pourriez avoir un comportement inattendu jusqu'à et y compris l'exécution de commandes arbitraires.
Charles Duffy
6
Il est beaucoup, beaucoup plus sûr d'écrire A=1; B=1et if (( A && B ))- de les traiter comme numériques - ou [[ $A && $B ]], qui traite une chaîne vide comme fausse et une chaîne non vide comme vraie.
Charles Duffy
3
(notez que je n'utilise que les noms de variables en majuscules ci-dessus pour suivre les conventions établies par la réponse - POSIX spécifie en fait explicitement les noms en majuscules à utiliser pour les variables d'environnement et les modules internes du shell, et réserve les noms en minuscules pour l'utilisation de l'application; pour éviter les conflits avec les futurs environnements OS, d'autant plus que la définition d'une variable shell écrase toute variable d'environnement portant le même nom, il est toujours idéal d'honorer cette convention dans ses propres scripts).
Charles Duffy
Merci pour les informations!
Rodrigo
2

L'utilisation de vrai / faux supprime un certain encombrement de parenthèses ...

#! /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
la source
J'ai trouvé cela utile mais dans ubuntu 18.04 $ 0 était "-bash" et la commande basename a généré une erreur. Changer ce test pour que [[ "$0" =~ "bash" ]] le script fonctionne pour moi.
WiringHarness