Probar si se establecen múltiples variables

19

Me gustaría asegurarme de que en un determinado punto de un script, después sourcede crear un archivo de configuración, se establezcan varias variables y, si no lo están, para detener la ejecución, informar al usuario sobre la variable que falta. Yo he tratado

for var in $one $two $three ; do
    ...

pero si, por ejemplo, $twono está configurado, el bucle nunca se ejecuta para $two. Lo siguiente que intenté fue

for var in one two three ; do
    if [ -n ${!var} ] ; then
        echo "$var is set to ${!var}"
    else
        echo "$var is not set"
    fi
done

Pero si dos no está configurado, sigo obteniendo "dos está configurado en" en lugar de "dos no está configurado".

¿Cómo puedo asegurarme de que todas las variables requeridas estén establecidas?

Actualización / Solución: Sé que hay una diferencia entre "set" y "set, pero vacío". Ahora uso (gracias a /programming//a/16753536/3456281 y las respuestas a esta pregunta) lo siguiente:

if [ -n "${!var:-}" ] ; then

entonces, si varestá configurado pero vacío, todavía se considera inválido.

Jaspe
fuente
1
También puede agregar set -ual comienzo de su script para terminarlo inmediatamente cuando se utiliza una variable no establecida.
n.st

Respuestas:

8

Error de cita

if [ -n "${!var}" ] ; then

Para el futuro: configuración

set -x

antes de ejecutar el código le habría mostrado el problema. En lugar de agregar eso al código, puede llamar a su script con

bash -vx ./my/script.sh
Hauke ​​Laging
fuente
Esto funciona, pero ¿qué está pasando con / sin comillas? ¿Es mi enfoque general correcto en primer lugar?
Jasper
Sí, eso es precognitivo: respondí la pregunta antes de que se hiciera ... 8-)
Hauke ​​Laging
44
@Jasper Usted debe siempre citar variables. Eso no cuesta más tiempo que pensar si esto es necesario cada vez. Pero evita los errores.
Hauke ​​Laging
Por el contrario, las citas solo protegen contra la expansión. Si la expansión es lo que le interesa, las citas ciertamente no son el camino a seguir. Por ejemplo: cs = 673,290,765; conjunto - $ (IFS =,; echo $ cs); echo $ #; salida: 3
mikeserv
2
@mikeserv Solo las comillas simples protegen contra la expansión de lo que obviamente no sugiero. Las comillas dobles protegen contra la división de palabras. No he negado que haya casos en los que deba omitirse la cita. Pero ese no es un argumento en contra de mi regla general. ¿Qué tipo de gente va a usar algo así set -- $(IFS=, ; echo $cs)? El tipo de personas que tienen que preguntar aquí ¿por qué if [ -n ${!var} ] ; thenno funciona? Probablemente no.
Hauke ​​Laging
5

Lo único que necesita son citas en su prueba:

for var in one two three ; do
    if [ -n "${!var}" ] ; then
        echo "$var is set to ${!var}"
    else
        echo "$var is not set"
    fi
done

Funciona para mi.

TNW
fuente
4

Si desea que el programa se detenga:

N= 
${one?var 1 is unset} 
${two:?var 2 is unset or null}
${three:+${N:?var 3 is set and not null}}

Eso hará el truco. Cada uno de los mensajes que siguen al signo de interrogación se imprime stderry el shell principal muere. Bueno, está bien, así que no cada mensaje, solo uno, solo el primero que falla imprime un mensaje porque el shell muere. Me gusta usar estas pruebas así:

( for v in "$one" "$two" "$three" ; do
    i=$((i+1)) ; : ${v:?var $i is unset or null...} 
done ) || _handle_it

Tenía mucho más que decir sobre esto aquí .

mikeserv
fuente
2

Puedes añadir

set -u

al comienzo de su secuencia de comandos para que finalice cuando intente utilizar una variable no establecida.

Un guión como

#!/bin/sh
set -u
echo $foo

resultará en

script.sh: 3: script.sh: foo: parámetro no establecido

Si está utilizando en su bashlugar, el error se verá así:

script.sh: línea 3: foo: variable independiente

n.st
fuente
¿Y en su opinión esto tiene sentido para las comprobaciones de tiempo de ejecución?
Hauke ​​Laging
2
@HaukeLaging No te sigo del todo: set -uevita exactamente el tipo de error que el OP está tratando de evitar y (al contrario de todas las otras soluciones) no se limita a un conjunto específico de variables. De hecho, es una precaución útil para casi todos los scripts de shell que fallen de manera segura en lugar de hacer cosas inesperadas cuando una variable no está configurada. Este artículo es una referencia útil sobre el tema.
n.st
@ n.st No estoy de acuerdo: nulo puede ser un valor tan útil como no nulo si lo planea.
mikeserv
1
@ n.st Esto no tiene ningún sentido para el OP ya que no quiere una protección contra el acceso a variables no establecidas (por cierto: una variable establecida pero vacía causaría el mismo error pero no reaccionaría a su "protección"). Quiere un tiempo de ejecución para verificar si una variable está desarmado / vacío. Su sugerencia puede ser útil como ayuda general para el desarrollo, pero no resuelve el problema del OP.
Hauke ​​Laging
set -uCreo que podría usarse también, pero no es tan flexible como la expansión de parámetros adecuada (vea mi pregunta actualizada). Y con set -utendría que hacer un acceso ficticio a todas las variables de destino si quiero verificar su presencia en un solo lugar.
Jasper
2

Una solución que es máximamente amigable prueba todos los requisitos y los informa en conjunto, en lugar de fallar en el primero y requerir de ida y vuelta para hacer las cosas bien:

#!/bin/bash

required_vars=(one two three)

missing_vars=()
for i in "${required_vars[@]}"
do
    test -n "${!i:+y}" || missing_vars+=("$i")
done
if [ ${#missing_vars[@]} -ne 0 ]
then
    echo "The following variables are not set, but should be:" >&2
    printf ' %q\n' "${missing_vars[@]}" >&2
    exit 1
fi

Utilizo una variable de matriz para rastrear qué variables no se han establecido y utilizo el resultado para redactar un mensaje orientado al usuario.

Notas:

  • Cité ${required_vars[@]}en el forciclo principalmente por costumbre: ¡no recomendaría incluir metacaracteres de shell en sus nombres de variables!
  • No cité ${#missing_vars[@]}, porque eso siempre es un número entero, incluso si eres lo suficientemente perverso como para ignorar el consejo anterior.
  • Yo solía %qal imprimir; %snormalmente sería suficiente
  • La salida de error siempre va a la secuencia de error >&2, por lo que no se canaliza a los comandos posteriores
  • El silencio es dorado: no imprima información de progreso o información de depuración a menos que se le solicite específicamente. Eso hace que los errores sean más obvios.
Toby Speight
fuente
1

bash4.2 le permite probar si una variable se establece con el -voperador; una variable no establecida y una variable establecida en la cadena vacía son dos condiciones diferentes:

$ unset foo
$ [[ -v foo ]] && echo foo is set
$ [[ -z "$foo" ]] && echo foo is empty
foo is empty
$ foo=
$ [[ -v foo ]] && echo foo is set
foo is set
$ [[ -z "$foo" ]] && echo foo is empty
foo is empty
chepner
fuente
Estaba preguntando sobre variables "múltiples", así que estaba buscando algo como indirección ...
Jasper
No es necesario indirecta, ya que -vtoma el nombre de una variable, por lo for var in one two three; [[ -v $var ]] && ...; doneque comprobar si cada uno de one, twoy threese ponen en secuencia.
chepner
1

Creo que si quieres decir not set, entonces la variable nunca debe inicializarse. Si usa [ -n "${!var}" ], entonces la variable vacía como two=""fallará, mientras está configurada . Puedes probar esto:

one=1
three=3

for var in one two three; do
  declare -p $var > /dev/null 2>&1 \
  && printf '%s is set to %s\n' "$var" "${!var}" \
  || printf '%s is not set\n' "$var"
done
Cuonglm
fuente
0

Una solución concisa (aunque ligeramente hacky), para manejar el caso en el que está bien que el sourcescript d establezca las variables en un valor nulo, es inicializar las variables a algún valor especial antes de obtener el script, y luego verificar ese valor después , p.ej

one=#UNSET#
two=#UNSET#
three=#UNSET#

. set_vars_script

if [[ $one$two$three == *#UNSET#* ]] ; then
  echo "One or more essential variables are unset" >&2
  exit 1
fi
Laurence Renshaw
fuente
1
Otra buena solución, pero me gustaría dar un mensaje de error más específico y luego "una o más variables sin establecer" ...
Jasper
0

He extendido la respuesta de @ mikeserv .

Esta versión toma una lista de variables para verificar la presencia, imprime el nombre de la variable que falta y activa una llamada al uso () en caso de error.

REQD_VALUES=("VARIABLE" "FOO" "BAR" "OTHER_VARIABLE")
( i=0; for var_name in ${REQD_VALUES[@]}; do
    VALUE=${!var_name} ;
    i=$((i+1)) ; : ${VALUE:?$var_name is missing}
done ) || usage

Ejemplo de salida cuando falta un valor:

./myscript.sh: line 42: VALUE: OTHER_VARIABLE is missing

Tenga en cuenta que puede cambiar la variable llamada VALOR a un nombre alternativo que se adapte mejor a su salida deseada.

Ryan
fuente