¿Cuál es una forma concisa de verificar que las variables de entorno estén establecidas en un script de shell de Unix?

500

Tengo algunos scripts de shell de Unix donde necesito verificar que ciertas variables de entorno estén configuradas antes de comenzar a hacer cosas, así que hago este tipo de cosas:

if [ -z "$STATE" ]; then
    echo "Need to set STATE"
    exit 1
fi  

if [ -z "$DEST" ]; then
    echo "Need to set DEST"
    exit 1
fi

que es mucho escribir ¿Existe una expresión más elegante para verificar que se establece un conjunto de variables de entorno?

EDITAR: Debo mencionar que estas variables no tienen un valor predeterminado significativo: el script debe generar un error si alguno no está configurado.

AndrewR
fuente
2
Muchas de las respuestas a esta pregunta se sienten como algo que vería en Code Golf Stack Exchange .
Daniel Schilling

Respuestas:

587

Expansión de parámetros

La respuesta obvia es usar una de las formas especiales de expansión de parámetros:

: ${STATE?"Need to set STATE"}
: ${DEST:?"Need to set DEST non-empty"}

O, mejor (vea la sección 'Posición de comillas dobles' a continuación):

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

La primera variante (usando solo ?) requiere que se establezca STATE, pero STATE = "" (una cadena vacía) está bien, no exactamente lo que desea, sino la notación alternativa y anterior.

La segunda variante (usando :?) requiere que DEST esté configurado y no esté vacío.

Si no proporciona ningún mensaje, el shell proporciona un mensaje predeterminado.

La ${var?}construcción es portátil de nuevo a la Versión 7 UNIX y Bourne Shell (1978 o más o menos). La ${var:?}construcción es un poco más reciente: creo que estaba en System III UNIX alrededor de 1981, pero puede haber estado en PWB UNIX antes de eso. Por lo tanto, está en Korn Shell y en los shells POSIX, incluido específicamente Bash.

Por lo general, se documenta en la página de comando man de la shell en una sección llamada Expansión de parámetros . Por ejemplo, el bashmanual dice:

${parameter:?word}

Mostrar error si es nulo o desarmado. Si el parámetro es nulo o no está establecido, la expansión de la palabra (o un mensaje a tal efecto si la palabra no está presente) se escribe en el error estándar y el shell, si no es interactivo, se cierra. De lo contrario, el valor del parámetro se sustituye.

El comando de colon

Probablemente debería agregar que el comando de dos puntos simplemente tiene sus argumentos evaluados y luego tiene éxito. Es la notación de comentario de shell original (antes de ' #' al final de la línea). Durante mucho tiempo, los scripts de shell Bourne tenían dos puntos como primer carácter. El C Shell leería un script y usaría el primer carácter para determinar si era para el C Shell (un #"hash" o el shell Bourne (un :"dos puntos"). Luego, el kernel entró en acción y agregó soporte para " #!/path/to/programy el shell Bourne recibió #comentarios", y la convención de colon quedó en el camino. Pero si te encuentras con un script que comienza con dos puntos, ahora sabrás por qué.


Posición de comillas dobles

Blong preguntó en un comentario :

¿Alguna idea sobre esta discusión? https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749

La esencia de la discusión es:

... Sin embargo, cuando lo hago shellcheck(con la versión 0.4.1), recibo este mensaje:

In script.sh line 13:
: ${FOO:?"The environment variable 'FOO' must be set and non-empty"}
  ^-- SC2086: Double quote to prevent globbing and word splitting.

¿Algún consejo sobre lo que debo hacer en este caso?

La respuesta corta es "haz lo que shellchecksugieras":

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

Para ilustrar por qué, estudie lo siguiente. Tenga en cuenta que el :comando no hace eco de sus argumentos (pero el shell evalúa los argumentos). Queremos ver los argumentos, por lo que el siguiente código se utiliza printf "%s\n"en lugar de :.

$ mkdir junk
$ cd junk
$ > abc
$ > def
$ > ghi
$ 
$ x="*"
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
abc
def
ghi
$ unset x
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
bash: x: You must set x
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
bash: x: You must set x
$ x="*"
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
*
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
abc
def
ghi
$ x=
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ unset x
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ 

Observe cómo el valor en $xse expande primero *y luego una lista de nombres de archivo cuando la expresión general no está entre comillas dobles. Esto es lo que shellcheckse recomienda que se arregle. No he verificado que no se oponga al formulario donde la expresión está entre comillas dobles, pero es una suposición razonable que estaría bien.

Jonathan Leffler
fuente
2
Eso es lo que necesito. He estado usando varias versiones de Unix desde 1987 y nunca he visto esta sintaxis, solo muestra ...
AndrewR el
¿Cómo funciona esto y dónde está documentado? Me pregunto si se puede modificar para verificar si la variable existe y se establece en un valor específico.
jhabbott
66
Está documentado en la página del manual de shell o en el manual de Bash, generalmente bajo el encabezado 'Expansión de parámetros'. La manera estándar para comprobar si existe la variable y se establece en un valor específico (digamos 'abc') es simplemente: if [ "$variable" = "abc" ]; then : OK; else : variable is not set correctly; fi.
Jonathan Leffler
¿Cómo uso esta sintaxis con una variable de cadena con espacios en ella? Recibo un error que dice "Esto: comando no encontrado" cuando configuro la variable que quiero verificar en "Esto es una prueba". ¿Algunas ideas?
user2294382
1
¿Alguna idea sobre esta discusión? github.com/koalaman/shellcheck/issues/…
blong
138

Prueba esto:

[ -z "$STATE" ] && echo "Need to set STATE" && exit 1;
David Schlosnagle
fuente
8
Eso es bastante detallado. El punto y coma es redundante.
Jonathan Leffler el
3
O incluso más corto, [ -z "$STATE" ] && { echo "Need to set STATE"; exit 1; } vea esta publicación de blog
zipizap
36
Lo que es eso es legible. Estoy a favor de este. ¡Los scripts de Bash necesitan toda la ayuda que puedan obtener en ese departamento!
Iain Duncan
La respuesta original es más consistente en su sintaxis.
diente rápido
44
Sin embargo, esto no distingue entre una variable no establecida y una variable establecida en la cadena vacía.
chepner
56

Su pregunta depende del shell que esté utilizando.

Bourne Shell deja muy poco en lo que busca.

PERO...

Funciona, casi en todas partes.

Solo trata de mantenerte alejado de csh. Fue bueno para las campanas y silbatos que agregó, en comparación con el shell Bourne, pero ahora es realmente crujiente. Si no me cree, ¡solo intente separar STDERR en csh! (-:

Hay dos posibilidades aquí. El ejemplo anterior, a saber, usando:

${MyVariable:=SomeDefault}

Por primera vez, debe consultar $ MyVariable. Esto toma el env. var MyVariable y, si actualmente no está configurado, asigna el valor de SomeDefault a la variable para su uso posterior.

También tienes la posibilidad de:

${MyVariable:-SomeDefault}

que solo sustituye SomeDefault por la variable donde está utilizando esta construcción. No asigna el valor SomeDefault a la variable, y el valor de MyVariable seguirá siendo nulo después de que se encuentre esta declaración.

Rob Wells
fuente
3
CSH: (foo> foo.out)> & foo.err
Mr.Ree
El shell Bourne hace lo que se requiere.
Jonathan Leffler
51

Seguramente el enfoque más simple es agregar el -uinterruptor al shebang (la línea en la parte superior de su script), suponiendo que esté usando bash:

#!/bin/sh -u

Esto hará que la secuencia de comandos salga si hay alguna variable no ligada dentro.

Paul Makkar
fuente
40
${MyVariable:=SomeDefault}

Si MyVariablese establece y no es nulo, restablecerá el valor de la variable (= no sucede nada).
De lo contrario, MyVariablese establece en SomeDefault.

Lo anterior intentará ejecutarse ${MyVariable}, por lo que si solo desea establecer la variable, haga lo siguiente:

MyVariable=${MyVariable:=SomeDefault}
Vincent Van Den Berghe
fuente
Esto no genera el mensaje, y la Q dice que no hay un valor predeterminado (aunque no lo dijo cuando escribió su respuesta).
Jonathan Leffler el
10
Versiones correctas: (1): $ {MyVariable: = SomeDefault} o (2): MyVariable = $ {MyVariable: -SomeDefault}
Jonathan Leffler el
23

En mi opinión, la verificación más simple y compatible para #! / Bin / sh es:

if [ "$MYVAR" = "" ]
then
   echo "Does not exist"
else
   echo "Exists"
fi

Una vez más, esto es para / bin / sh y es compatible también en sistemas Solaris antiguos.

Adriano
fuente
Esto 'funciona', pero incluso los sistemas Solaris antiguos tienen un /bin/shsoporte para la ${var:?}notación, etc.
Jonathan Leffler
La mejor respuesta hasta ahora.
Ole el
44
Estar configurado en la cadena vacía es distinto de no estar configurado en absoluto. Esta prueba imprimirá "No existe" después de ambos unset MYVARy MYVAR=.
chepner
@chepner, voté por tu comentario para el presumiblemente valioso heads-up, pero luego lo probé (con bash), y en realidad no pude establecer un var en una cadena vacía sin que también estuviera desarmado. ¿Cómo lo harías tú?
Sz.
@Sz No estoy seguro de lo que quieres decir. "Unset" significa que el nombre no tiene ningún valor asignado. Si no fooestá configurado, "$foo"aún se expande a la cadena vacía.
chepner el
17

bash4.2 introdujo el -voperador que prueba si un nombre se establece en algún valor, incluso la cadena vacía.

$ unset a
$ b=
$ c=
$ [[ -v a ]] && echo "a is set"
$ [[ -v b ]] && echo "b is set"
b is set
$ [[ -v c ]] && echo "c is set"
c is set
chepner
fuente
16

Siempre usé:

if [ "x$STATE" == "x" ]; then echo "Need to set State"; exit 1; fi

No mucho más conciso, me temo.

Bajo CSH tienes $? ESTADO.

Mr.Ree
fuente
2
Esto comprueba si STATEestá vacío o sin configurar, no solo sin configurar.
chepner
7

Para las personas futuras como yo, quería dar un paso adelante y parametrizar el nombre de la variable, para poder recorrer una lista de nombres de variables de tamaño variable:

#!/bin/bash
declare -a vars=(NAME GITLAB_URL GITLAB_TOKEN)

for var_name in "${vars[@]}"
do
  if [ -z "$(eval "echo \$$var_name")" ]; then
    echo "Missing environment variable $var_name"
    exit 1
  fi
done
nicooga
fuente
3

Podemos escribir una buena afirmación para verificar un montón de variables a la vez:

#
# assert if variables are set (to a non-empty string)
# if any variable is not set, exit 1 (when -f option is set) or return 1 otherwise
#
# Usage: assert_var_not_null [-f] variable ...
#
function assert_var_not_null() {
  local fatal var num_null=0
  [[ "$1" = "-f" ]] && { shift; fatal=1; }
  for var in "$@"; do
    [[ -z "${!var}" ]] &&
      printf '%s\n' "Variable '$var' not set" >&2 &&
      ((num_null++))
  done

  if ((num_null > 0)); then
    [[ "$fatal" ]] && exit 1
    return 1
  fi
  return 0
}

Invocación de muestra:

one=1 two=2
assert_var_not_null one two
echo test 1: return_code=$?
assert_var_not_null one two three
echo test 2: return_code=$?
assert_var_not_null -f one two three
echo test 3: return_code=$? # this code shouldn't execute

Salida:

test 1: return_code=0
Variable 'three' not set
test 2: return_code=1
Variable 'three' not set

Más afirmaciones de este tipo aquí: https://github.com/codeforester/base/blob/master/lib/assertions.sh

codeforester
fuente
1

Ninguna de las soluciones anteriores funcionó para mis propósitos, en parte porque comprobé el entorno en busca de una lista abierta de variables que deben establecerse antes de comenzar un proceso largo. Terminé con esto:

mapfile -t arr < variables.txt

EXITCODE=0

for i in "${arr[@]}"
do
   ISSET=$(env | grep ^${i}= | wc -l)
   if [ "${ISSET}" = "0" ];
   then
      EXITCODE=-1
      echo "ENV variable $i is required."
   fi
done

exit ${EXITCODE}
Gudlaugur Egilsson
fuente
-1

En lugar de usar scripts de shell externos, tiendo a cargar funciones en mi shell de inicio de sesión. Utilizo algo como esto como una función auxiliar para verificar las variables de entorno en lugar de cualquier variable establecida:

is_this_an_env_variable ()
    local var="$1"
    if env |grep -q "^$var"; then
       return 0
    else
       return 1
    fi
 }
nafdef
fuente
1
Esto está mal. is_this_an_env_variable Pdevolverá el éxito porque PATHexiste.
Eric
Existe porque es una variable de entorno. Si se ha establecido una cadena no nula eqaul es otra cuestión.
nafdef
-7

La $?sintaxis es bastante clara:

if [ $?BLAH == 1 ]; then 
    echo "Exists"; 
else 
    echo "Does not exist"; 
fi
Graeme
fuente
Esto funciona, pero ¿alguien aquí sabe dónde puedo encontrar documentos sobre esa sintaxis? PS es normalmente el valor de retorno anterior.
Danny Staple
Lo malo es que esto no parece funcionar. Pruébelo en un script simple, con y sin ese conjunto.
Danny Staple
11
En los shells Bourne, Korn, Bash y POSIX, se $?encuentra el estado de salida del comando anterior; $?BLAHes la cadena que consiste en el estado de salida del comando anterior concatenado con 'BLAH'. Esto no prueba la variable $BLAHen absoluto. (Hay un rumor de que podría hacer más o menos lo que se requiere csh, pero es mejor dejar las conchas marinas en la orilla del mar).
Jonathan Leffler
Esta es una respuesta completamente incorrecta en lo que respecta a Bash.
codeforester