Bash si [falso]; devuelve verdadero

151

He estado aprendiendo bash esta semana y me encontré con un problema.

#!/bin/sh

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

Esto siempre dará como resultado True aunque la condición parezca indicar lo contrario. Si elimino los corchetes [], funciona, pero no entiendo por qué.

tenmiles
fuente
3
Aquí hay algo para comenzar.
keyser
10
Por cierto, un script que comienza #!/bin/shno es un script bash, es un script POSIX sh. Incluso si bash proporciona el intérprete de POSIX sh en su sistema, desactiva un montón de extensiones. Si desea escribir scripts de bash, use #!/bin/basho su equivalente apropiado localmente ( #!/usr/bin/env bashpara usar el primer intérprete de bash en PATH).
Charles Duffy
Esto [[ false ]]también afecta .
CMCDragonkai

Respuestas:

192

Está ejecutando el comando [(alias test) con el argumento "falso", no está ejecutando el comando false. Como "falso" es una cadena no vacía, el testcomando siempre tiene éxito. Para ejecutar realmente el comando, suelte el [comando.

if false; then
   echo "True"
else
   echo "False"
fi
chepner
fuente
44
La idea de que falso sea un comando, o incluso una cadena, me parece extraña, pero supongo que funciona. Gracias.
Tenmiles
31
bashno tiene ningún tipo de datos booleanos, por lo que no hay palabras clave que representen verdadero y falso. La ifdeclaración simplemente verifica si el comando que le das tiene éxito o falla. El testcomando toma una expresión y tiene éxito si la expresión es verdadera; una cadena no vacía es una expresión que se evalúa como verdadera, al igual que en la mayoría de los otros lenguajes de programación. falseEs un comando que siempre falla. (Por analogía, truees un comando que siempre tiene éxito.)
chepner
2
if [[ false ]];devuelve cierto también. Como lo hace if [[ 0 ]];. Y if [[ /usr/bin/false ]];tampoco se saltea el bloque. ¿Cómo puede falso o 0 evaluar a distinto de cero? Maldición, esto es frustrante. Si es importante, OS X 10.8; Bash 3.
jww
77
No se confunda falsecon una constante booleana o 0un literal entero; ambos son solo cadenas ordinarias. [[ x ]]es equivalente a [[ -n x ]]cualquier cadena xque no comience con un guión. bashno tiene ningún constantes booleanas en cualquier contexto. Las cadenas que parecen enteros se tratan como tales dentro (( ... ))o con operadores como -eqo -ltdentro [[ ... ]].
chepner
2
@ tbc0, hay más preocupaciones a la mano que solo cómo se lee algo. Si las variables se establecen mediante un código que no está completamente bajo su control (o que se puede manipular externamente, ya sea mediante nombres de archivo inusuales o de otro modo), if $predicatese puede abusar fácilmente para ejecutar código hostil de una manera que if [[ $predicate ]]no se puede.
Charles Duffy
55

Una cartilla booleana rápida para Bash

La ifafirmación adquiere un comando como argumento (como lo hacen &&, ||etc.). El código de resultado entero del comando se interpreta como booleano (0 / nulo = verdadero, 1 / más = falso).

La testdeclaración toma operadores y operandos como argumentos y devuelve un código de resultado en el mismo formato que if. Es un alias de la testdeclaración [, que a menudo se usa ifpara realizar comparaciones más complejas.

Las declaraciones truey falseno hacen nada y devuelven un código de resultado (0 y 1, respectivamente). Por lo tanto, pueden usarse como literales booleanos en Bash. Pero si coloca las declaraciones en un lugar donde se interpretan como cadenas, se encontrará con problemas. En tu caso:

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

Entonces:

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;

Esto es similar a hacer algo así como echo '$foo'frente echo "$foo".

Cuando se usa la testdeclaración, el resultado depende de los 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)

En resumen , si solo desea probar algo como pasar / fallar (también conocido como "verdadero" / "falso"), entonces pase un comando a su declaración ifo &&etc., sin corchetes. Para comparaciones complejas, use corchetes con los operadores adecuados.

Y sí, soy consciente de que no hay tal cosa como un tipo booleano nativo en Bash, y que ife [y trueson técnicamente "comandos" y no "declaraciones"; Esta es solo una explicación muy básica y funcional.

Beejor
fuente
1
Para su información, si desea usar 1/0 como Verdadero / Falso en su código, esto if (( $x )); then echo "Hello"; fimuestra el mensaje para x=1pero no para x=0o x=(indefinido).
Jonathan H
3

Descubrí que puedo hacer algo de lógica básica ejecutando algo como:

A=true
B=true
if ($A && $B); then
    C=true
else
    C=false
fi
echo $C
Rodrigo
fuente
66
Uno puede , pero es una muy mala idea. ( $A && $B )es una operación sorprendentemente ineficiente: genera un subproceso mediante una llamada fork(), luego divide en cadena los contenidos de $Apara generar una lista de elementos (en este caso de tamaño uno) y evalúa cada elemento como un globo (en este caso uno que se evalúa a sí mismo), luego ejecuta el resultado como un comando (en este caso, un comando incorporado).
Charles Duffy
3
Además, si su truey sus falsecadenas provienen de otro lugar, existe el riesgo de que realmente puedan contener contenidos distintos de trueo false, y podría obtener un comportamiento inesperado que puede incluir la ejecución arbitraria de comandos.
Charles Duffy
66
Es mucho, mucho más seguro escribir A=1; B=1y if (( A && B )), tratándolos como numéricos, o [[ $A && $B ]], que trata una cadena vacía como falsa y una cadena no vacía como verdadera.
Charles Duffy
3
(tenga en cuenta que solo estoy usando los nombres de variables en mayúsculas para seguir las convenciones establecidas por la respuesta: POSIX en realidad especifica explícitamente los nombres en mayúsculas que se usarán para las variables de entorno y los componentes integrados de shell, y se reservan los nombres en minúsculas para el uso de la aplicación; para evitar conflictos con futuros entornos de SO, particularmente dado que establecer una variable de shell sobrescribe cualquier variable de entorno con un nombre similar, siempre es ideal honrar esa convención en los propios scripts).
Charles Duffy
Gracias por las ideas!
Rodrigo
2

El uso de verdadero / falso elimina el desorden de paréntesis ...

#! /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
fuente
Esto me pareció útil, pero en Ubuntu 18.04 $ 0 era "-bash" y el comando basename arrojó un error. Cambiar esa prueba para que [[ "$0" =~ "bash" ]] el guión funcione para mí.
WiringHarness