Devolver un booleano de una función Bash

211

Quiero escribir una función bash que verifique si un archivo tiene ciertas propiedades y devuelve verdadero o falso. Entonces puedo usarlo en mis scripts en el "si". ¿Pero qué debo devolver?

function myfun(){ ... return 0; else return 1; fi;}

entonces lo uso así:

if myfun filename.txt; then ...

Por supuesto, esto no funciona. ¿Cómo se puede lograr esto?

luca
fuente
3
suelte la functionpalabra clave, myfun() {...}basta
Glenn Jackman
2
Lo que importa ifes el estado de salida cero de myfun: si myfunsale con 0, then ...se ejecuta; si es algo más else ... se ejecuta.
Eelvex
77
@nhed: la functionpalabra clave es un bashismo y provocará errores de sintaxis en algunos otros shells. Básicamente, es innecesario o está prohibido, entonces, ¿por qué usarlo? Ni siquiera es útil como un objetivo grep, ya que podría no estar allí (grep para su ()lugar).
Gordon Davisson
3
@GordonDavisson: ¿qué? hay otras conchas? ;-)
nhed 01 de
No utilice 0 y 1. Consulte stackoverflow.com/a/43840545/117471
Bruno Bronosky, el

Respuestas:

333

Use 0 para verdadero y 1 para falso.

Muestra:

#!/bin/bash

isdirectory() {
  if [ -d "$1" ]
  then
    # 0 = true
    return 0 
  else
    # 1 = false
    return 1
  fi
}


if isdirectory $1; then echo "is directory"; else echo "nopes"; fi

Editar

Del comentario de @amichair, estos también son posibles

isdirectory() {
  if [ -d "$1" ]
  then
    true
  else
    false
  fi
}


isdirectory() {
  [ -d "$1" ]
}
Erik
fuente
44
No, no necesita hacer eso, vea la muestra.
Erik
46
Para una mejor legibilidad, puede usar el comando 'verdadero' (que no hace nada y se completa con éxito, es decir, devuelve 0) y el comando 'falso' (que no hace nada y se completa sin éxito, es decir, devuelve un valor distinto de cero). Además, una función que finaliza sin una declaración de retorno explícita devuelve el código de salida del último comando ejecutado, por lo que en el ejemplo anterior, el cuerpo de la función se puede reducir a solo [ -d "$1" ].
amichair
24
Bengt: tiene sentido cuando lo piensas como "código de error": código de error 0 = todo salió bien = 0 errores; código de error 1 = lo principal que se suponía que debía hacer esta llamada falló; más: ¡falla! búscalo en la página del manual.
ovejas voladoras
77
Tiene sentido cuando se considera que, en la programación, las cosas generalmente solo pueden tener éxito de una manera, pero pueden fallar de infinitas maneras. Bueno, tal vez no infinito, pero muchas, las probabilidades están en contra de nosotros. Éxito / Error (es) no es booleano. Creo que esto "usa 0 para verdadero y 1 para falso". debería leer "Use 0 para el éxito y no cero para el fracaso".
Davos
66
No utilice 0 y 1. Consulte stackoverflow.com/a/43840545/117471
Bruno Bronosky, el
167

¿Por qué debería importarle lo que digo a pesar de que hay una respuesta de más de 250 votos?

No es eso 0 = truey 1 = false. Es: cero significa que no hay falla (éxito) y no cero significa falla (de tipo N) .

Si bien la respuesta seleccionada es técnicamente "verdadera" , no ponga return 1** en su código como falso . Tendrá varios efectos secundarios desafortunados.

  1. Los desarrolladores experimentados lo verán como un aficionado (por la razón a continuación).
  2. Los desarrolladores experimentados no hacen esto (por todas las razones a continuación).
  3. Es propenso a errores.
    • Incluso los desarrolladores experimentados pueden confundir 0 y 1 como falso y verdadero respectivamente (por la razón anterior).
  4. Requiere (o alentará) comentarios extraños y ridículos.
  5. En realidad, es menos útil que los estados de retorno implícitos.

Aprende un poco de fiesta

El manual de bash dice (énfasis mío)

volver [n]

Provoca que una función de shell deje de ejecutarse y devuelva el valor n a su llamador. Si no se proporciona n , el valor de retorno es el estado de salida del último comando ejecutado en la función.

Por lo tanto, no tenemos que usar NUNCA 0 y 1 para indicar Verdadero y Falso. El hecho de que lo hagan es esencialmente un conocimiento trivial útil solo para depurar código, preguntas de entrevistas y hacer volar las mentes de los novatos.

El manual de bash también dice

de lo contrario, el estado de retorno de la función es el estado de salida del último comando ejecutado

El manual de bash también dice

( $? ) Se expande al estado de salida de la canalización en primer plano ejecutada más recientemente .

Whoa, espera. ¿Tubería? Pasemos al manual de bash una vez más.

Una tubería es una secuencia de uno o más comandos separados por uno de los operadores de control '|' o '| &'.

Si. Dijeron que 1 comando es una tubería. Por lo tanto, las 3 citas dicen lo mismo.

  • $? te dice lo que pasó la última vez.
  • Burbujea

Mi respuesta

Entonces, mientras @Kambus demostró que con una función tan simple, noreturn se necesita nada. Creo que era poco realista en comparación con las necesidades de la mayoría de las personas que leerán esto.

Por qué return ?

Si una función va a devolver el estado de salida de su último comando, ¿por qué usar return ? Porque hace que una función deje de ejecutarse.

Detener la ejecución en múltiples condiciones

01  function i_should(){
02      uname="$(uname -a)"
03
04      [[ "$uname" =~ Darwin ]] && return
05
06      if [[ "$uname" =~ Ubuntu ]]; then
07          release="$(lsb_release -a)"
08          [[ "$release" =~ LTS ]]
09          return
10      fi
11
12      false
13  }
14
15  function do_it(){
16      echo "Hello, old friend."
17  }
18
19  if i_should; then
20    do_it
21  fi

Lo que tenemos aquí es ...

La línea 04es un retorno explícito [-ish] verdadero porque el RHS de&& solo se ejecuta si el LHS era verdadero

La línea 09devuelve verdadero o falso que coincide con el estado de la línea08

La línea 13devuelve falso debido a la línea12

(Sí, esto se puede reducir, pero todo el ejemplo está ideado).

Otro patrón común

# Instead of doing this...
some_command
if [[ $? -eq 1 ]]; then
    echo "some_command failed"
fi

# Do this...
some_command
status=$?
if ! $(exit $status); then
    echo "some_command failed"
fi

Observe cómo establecer una statusvariable desmitifica el significado de $?. (Por supuesto, usted sabe lo que $?significa, pero alguien con menos conocimiento que usted tendrá que buscarlo en Google algún día. A menos que su código esté haciendo operaciones de alta frecuencia, muestre algo de amor , establezca la variable). Pero la verdadera conclusión es que "si no existe el estado "o viceversa" si el estado de salida "se puede leer en voz alta y explicar su significado. Sin embargo, ese último puede ser un poco ambicioso porque ver la palabra exitpodría hacerte pensar que está saliendo del script, cuando en realidad está saliendo del $(...)subshell.


** Si insiste absolutamente en usar return 1para falso, le sugiero que al menos lo use return 255en su lugar. Esto hará que tu futuro yo o cualquier otro desarrollador que deba mantener tu código se pregunte "¿por qué es 255?" Entonces al menos estarán prestando atención y tendrán una mejor oportunidad de evitar un error.

Bruno Bronosky
fuente
1
@ZeroPhase 1 & 0 para falso y verdadero sería ridículo. Si tiene un tipo de datos binario, no hay razón para eso. De lo que se trata en bash es de un código de estado que refleja el éxito (singular) y los fracasos (plural). Es " iféxito hacer esto, elsehacer aquello". ¿Éxito en qué? Podría estar buscando verdadero / falso, podría estar buscando una cadena, un entero, un archivo, un directorio, permisos de escritura, glob, regex, grep o cualquier otro comando que esté sujeto a fallas .
Bruno Bronosky
3
Evitar el uso estándar de 0/1 como valor de retorno solo porque es obtuso y propenso a la confusión es una tontería. Todo el lenguaje shell es obtuso y propenso a la confusión. Bash sí utiliza la convención de 0/1 = verdadero / falso en su propia truey falsecomandos. Es decir, la palabra clave trueliteralmente se evalúa como un código de estado 0. Además, las declaraciones if-then por naturaleza operan en booleanos, no en códigos de éxito. Si ocurre un error durante una operación booleana, no debería devolver verdadero o falso, sino simplemente interrumpir la ejecución. De lo contrario, obtienes falsos positivos (juego de palabras).
Beejor
1
"if! $ (exit $ status); then" - Eso merece una mejor explicación. No es intuitivo Tengo que pensar si el programa se cerrará antes de imprimir el mensaje, o no. El resto de su respuesta fue buena, pero eso lo echó a perder.
Craig Hicks
1
@BrunoBronosky Leí su respuesta y creo que entiendo la distinción entre la canalización de contenido (stdin-> stdout) y los códigos de error en los valores de retorno. Sin embargo, no entiendo por qué return 1debería evitarse el uso. Suponiendo que tengo una validatefunción, me parece razonable return 1si la validación falla. Después de todo, esa es una razón para detener la ejecución de un script si no se maneja correctamente (por ejemplo, cuando se usa set -e).
JepZ
1
@Jepz esa es una gran pregunta. Tienes razón, eso return 1es válido. Mi preocupación son todos los comentarios aquí que dicen "0 = verdadero 1 = falso es [insertar palabra negativa]". Es probable que esas personas lean su código algún día. Entonces, para ellos, ver return 255(o 42 o incluso 2) debería ayudarlos a pensarlo más y no confundirlo como "verdadero". set -eaún lo atrapará.
Bruno Bronosky
31
myfun(){
    [ -d "$1" ]
}
if myfun "path"; then
    echo yes
fi
# or
myfun "path" && echo yes
Kambus
fuente
1
¿Qué hay de la negación?
einpoklum
¿Qué hay de eso, @einpoklum?
Mark Reed
@ MarkReed: quise decir, agrega el caso "else" a tu ejemplo.
einpoklum
myfun "camino" || echo no
Hrobky
13

Tenga cuidado al verificar el directorio solo con la opción -d!
Si la variable $ 1 está vacía, el cheque seguirá siendo exitoso. Para estar seguro, verifique también que la variable no esté vacía.

#! /bin/bash

is_directory(){

    if [[ -d $1 ]] && [[ -n $1 ]] ; then
        return 0
    else
        return 1
    fi

}


#Test
if is_directory $1 ; then
    echo "Directory exist"
else
    echo "Directory does not exist!" 
fi
RenRen
fuente
1
No estoy seguro de cómo responde esto a la pregunta formulada. Si bien es bueno saber que un $ 1 vacío puede devolver un verdadero cuando está vacío, no proporciona ninguna idea de cómo devolver verdadero o falso de una función bash. Sugeriría crear una nueva pregunta "¿Qué sucede cuando haces una prueba en una variable de shell vacía?" Y luego publicar esto como la respuesta.
DRaehal
3
Tenga en cuenta que si agrega una cita correcta a $1( "$1"), entonces no necesita verificar si hay una variable vacía. El [[ -d "$1" ]]fallarían porque este ""no es un directorio.
morgents
3

Encontré un punto (¿aún no mencionado explícitamente?) Que me estaba tropezando. Es decir, no cómo devolver el valor booleano, sino cómo evaluarlo correctamente.

Estaba tratando de decir if [ myfunc ]; then ..., pero eso simplemente está mal. ¡No debes usar los soportes! if myfunc; then ...es la forma de hacerlo

¡Como en @Bruno y otros reiteraron, truey falseson comandos , no valores! Eso es muy importante para comprender los booleanos en los scripts de shell.

En esta publicación, expliqué y demostré usando variables booleanas : https://stackoverflow.com/a/55174008/3220983 . Sugiero comprobar esto, porque está muy relacionado.

Aquí, proporcionaré algunos ejemplos de devolución y evaluación de booleanos de funciones:

Esta:

test(){ false; }                                               
if test; then echo "it is"; fi                                 

No produce salida de eco. (es decir, false devuelve falso)

test(){ true; }                                                
if test; then echo "it is"; fi                                 

Produce:

it is                                                        

(es decir, true devuelve verdadero)

Y

test(){ x=1; }                                                
if test; then echo "it is"; fi                                 

Produce:

it is                                                                           

Porque 0 (es decir, verdadero) se devolvió implícitamente .

Ahora, esto es lo que me estaba jodiendo ...

test(){ true; }                                                
if [ test ]; then echo "it is"; fi                             

Produce:

it is                                                                           

Y

test(){ false; }                                                
if [ test ]; then echo "it is"; fi                             

TAMBIÉN produce:

it is                                                                           

¡Usar los corchetes aquí produjo un falso positivo ! (Infiero que el resultado del comando "externo" es 0.)

La principal conclusión de mi publicación es: ¡no use corchetes para evaluar una función booleana (o variable) como lo haría para una verificación de igualdad típica, por ejemplo if [ x -eq 1 ]; then...!

BuvinJ
fuente
2

Use los comandos trueo falseinmediatamente antes de su return, luego returnsin parámetros. El returnutilizará automáticamente el valor de su último comando.

Proporcionar argumentos returnes inconsistente, escriba un tipo específico y es propenso a errores si no está usando 1 o 0. Y como han dicho los comentarios anteriores, usar 1 o 0 aquí no es la forma correcta de abordar esta función.

#!/bin/bash

function test_for_cat {
    if [ $1 = "cat" ];
    then
        true
        return
    else
        false
        return
    fi
}

for i in cat hat;
do
    echo "${i}:"
    if test_for_cat "${i}";
    then
        echo "- True"
    else
        echo "- False"
    fi
done

Salida:

$ bash bash_return.sh

cat:
- True
hat:
- False
mrteatime
fuente
1

Podría funcionar si reescribe esto function myfun(){ ... return 0; else return 1; fi;}como esto function myfun(){ ... return; else false; fi;}. Es decir, si falsees la última instrucción en la función, obtiene un resultado falso para la función completa, pero de returntodos modos interrumpe la función con un resultado verdadero. Creo que es cierto para mi intérprete bash al menos.

Sobrino
fuente
1

Encontré que la forma más corta para probar la salida de la función es simplemente

do_something() {
    [[ -e $1 ]] # e.g. test file exists
}

do_something "myfile.txt" || { echo "File doesn't exist!"; exit 1; }
Roy Shilkrot
fuente
1

Por razones de legibilidad del código, creo que devolver verdadero / falso debería:

  • estar en una línea
  • ser un comando
  • ser fácil de recordar
  • mencionar la palabra clave returnseguida de otra palabra clave ( trueo false)

Mi solución es return $(true)o return $(false)como se muestra:

is_directory()
{
    if [ -d "${1}" ]; then
        return $(true)
    else
        return $(false)
    fi
}
Charlie
fuente
0

Siguiendo a @Bruno Bronosky y @mrteatime, le sugiero que escriba su retorno booleano "al revés". Esto es lo que quiero decir:

foo()
{
    if [ "$1" == "bar" ]; then
        true; return
    else
        false; return
    fi;
}

Eso elimina el requisito de dos líneas feas para cada declaración de devolución.

BuvinJ
fuente