Cómo saber si una cadena no está definida en un script de shell Bash

151

Si quiero verificar la cadena nula que haría

[ -z $mystr ]

pero ¿qué pasa si quiero verificar si la variable se ha definido? ¿O no hay distinción en las secuencias de comandos Bash?

Setjmp
fuente
27
tenga la costumbre de usar [-z "$ mystr"] en lugar de [-z $ mystr]
Charles Duffy el
¿Cómo hacer lo inverso? Quiero decir, cuando la cadena no es nula
abre el camino el
1
@flow: ¿Qué pasa?[ -n "${VAR+x}"] && echo not null
Lekensteyn
1
@CharlesDuffy He leído esto antes en muchos recursos en línea. ¿Por qué se prefiere esto?
incipiente
66
@Ayos porque si no tiene comillas, el contenido de la variable se divide en cadenas y se engloba; si es una cadena vacía, se convierte en [ -z ]lugar de [ -z "" ]; si tiene espacios, se convierte en [ -z "my" "test" ]lugar de [ -z my test ]; y si es así [ -z * ], *se reemplaza por los nombres de los archivos en su directorio.
Charles Duffy

Respuestas:

138

Creo que la respuesta que busca está implícita (si no se indica) en la respuesta de Vinko , aunque no se explica simplemente. Para distinguir si VAR está configurado pero vacío o no, puede usar:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Probablemente pueda combinar las dos pruebas en la segunda línea en una con:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Sin embargo, si lee la documentación de Autoconf, encontrará que no recomiendan combinar términos con ' -a' y recomiendan usar pruebas simples separadas combinadas con &&. No he encontrado un sistema donde haya un problema; eso no significa que no existían (pero probablemente son extremadamente raros en estos días, incluso si no fueran tan raros en el pasado distante).

Puede encontrar los detalles de estas y otras expansiones de parámetros de shell relacionados , el comando testo[ y las expresiones condicionales en el manual de Bash.


Recientemente me preguntaron por correo electrónico sobre esta respuesta con la pregunta:

Usas dos pruebas, y entiendo bien la segunda, pero no la primera. Más precisamente, no entiendo la necesidad de expansión variable

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi

¿No lograría esto lo mismo?

if [ -z "${VAR}" ]; then echo VAR is not set at all; fi

Pregunta justa: la respuesta es 'No, su alternativa más simple no hace lo mismo'.

Supongamos que escribo esto antes de su prueba:

VAR=

Su prueba dirá "VAR no está configurado en absoluto", pero el mío dirá (por implicación porque no tiene eco) "VAR está configurado pero su valor puede estar vacío". Prueba este script:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo JL:1 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:1 VAR is not set at all; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo JL:2 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:2 VAR is not set at all; fi
)

El resultado es:

JL:1 VAR is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

En el segundo par de pruebas, la variable se establece, pero se establece en el valor vacío. Esta es la distinción que hacen las anotaciones ${VAR=value}y ${VAR:=value}. Lo mismo para ${VAR-value}y ${VAR:-value}, y ${VAR+value}y ${VAR:+value}, y así sucesivamente.


Como Gili señala en su respuesta , si corres bashcon la set -o nounsetopción, entonces la respuesta básica anterior falla unbound variable. Se remedia fácilmente:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

O puede cancelar la set -o nounsetopción con set +u(que set -ues equivalente a set -o nounset).

Jonathan Leffler
fuente
77
El único problema que tengo con esta respuesta es que cumple su tarea de una manera bastante indirecta y poco clara.
Suiza el
3
Para aquellos que desean buscar la descripción de lo que significa lo anterior en la página de manual de bash, busque la sección "Expansión de parámetros" y luego este texto: "Cuando no se realiza la expansión de subcadenas, utilizando los formularios documentados a continuación, las pruebas de bash para un parámetro que no está establecido o es nulo ['nulo' significa la cadena vacía]. Omitir los dos puntos resulta en una prueba solo para un parámetro que no está establecido (...) $ {parámetro: + palabra}: Usar valor alternativo. Si el parámetro es nulo o no establecido, nada se sustituye, de lo contrario, la expansión de la palabra se sustituye ".
David Tonhofer
2
@Swiss Esto no es claro ni indirecto, sino idiomático. Quizás para un programador que no esté familiarizado ${+}y ${-}no esté claro, pero la familiaridad con esas construcciones es esencial si se quiere ser un usuario competente del shell.
William Pursell
Consulte stackoverflow.com/a/20003892/14731 si desea implementar esto con set -o nounsethabilitado.
Gili
Hice muchas pruebas sobre esto; porque los resultados en mis guiones fueron inconsistentes. Sugiero que la gente busque el [ -v VAR ]enfoque " " con bash -s v4.2 y más allá .
será
38
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z también funciona para variables indefinidas. Para distinguir entre un indefinido y uno definido, usaría las cosas enumeradas aquí o, con explicaciones más claras, aquí .

La forma más limpia es usar la expansión como en estos ejemplos. Para obtener todas sus opciones, consulte la sección Expansión de parámetros del manual.

Palabra alternativa:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

Valor por defecto:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

Por supuesto, usaría uno de estos de manera diferente, colocando el valor que desea en lugar del 'valor predeterminado' y usando la expansión directamente, si corresponde.

Vinko Vrsalovic
fuente
1
Pero quiero distinguir si la cadena es "" o no se ha definido nunca. ¿Es eso posible?
Setjmp
1
esta respuesta no dice cómo distinguir entre los dos casos; siga el enlace de preguntas frecuentes de bash que proporciona para más discusión.
Charles Duffy
1
Agregué
2
Busque "Expansión de parámetros" en la página de manual de bash para ver todos estos "trucos". Por ejemplo, $ {foo: -default} para usar un valor predeterminado, $ {foo: = default} para asignar el valor predeterminado, $ {foo:? Mensaje de error} para mostrar un mensaje de error si foo no está configurado, etc.
Jouni K Seppänen
18

Guía avanzada de secuencias de comandos bash, 10.2. Sustitución de parámetros:

  • $ {var + blahblah}: si se define var, 'blahblah' se sustituye por la expresión, de lo contrario se sustituye nulo
  • $ {var-blahblah}: si se define var, se sustituye a sí mismo, de lo contrario, 'blahblah' se sustituye
  • $ {var? blahblah}: si se define var, se sustituye; de ​​lo contrario, la función existe con 'blahblah' como mensaje de error.


para basar la lógica de su programa en si la variable $ mystr está definida o no, puede hacer lo siguiente:

isdefined=0
${mystr+ export isdefined=1}

ahora, si isdefined = 0, entonces la variable no estaba definida, si isdefined = 1, la variable estaba definida

Esta forma de verificar las variables es mejor que la respuesta anterior porque es más elegante, legible, y si su shell bash se configuró para error en el uso de variables indefinidas (set -u), el script terminará prematuramente.


Otras cosas útiles:

tener un valor predeterminado de 7 asignado a $ mystr si no estaba definido, y dejarlo intacto de lo contrario:

mystr=${mystr- 7}

para imprimir un mensaje de error y salir de la función si la variable no está definida:

: ${mystr? not defined}

Tenga en cuenta aquí que usé ':' para no tener el contenido de $ mystr ejecutado como un comando en caso de que esté definido.


fuente
No sabía sobre el? sintaxis para las variables BASH. Esa es una buena captura.
Suiza el
Esto funciona también con referencias variables indirectas. Esto pasa: var= ; varname=var ; : ${!varname?not defined}y esto termina: varname=var ; : ${!varname?not defined}. Pero es un buen hábito de usar set -uque hace lo mismo mucho más fácil.
ceving
11

Un resumen de las pruebas.

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"
k107
fuente
Buena práctica en bash. A veces, las rutas de archivo tienen espacios o para proteger contra la inyección de comandos
k107
1
Creo que con ${var+x}esto no es necesario, excepto si se permite que los nombres de variables tengan espacios en ellos.
Anne van Rossum
No solo buenas prácticas; se requiere con [obtener resultados correctos. Si no varestá configurado o está vacío, se [ -n $var ]reduce a [ -n ]cuál tiene el estado de salida 0, mientras que [ -n "$var" ]tiene el estado de salida esperado (y correcto) de 1.
chepner
5

https://stackoverflow.com/a/9824943/14731 contiene una mejor respuesta (una que es más legible y funciona con set -o nounsethabilitada). Funciona más o menos así:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi
Gili
fuente
4

La forma explícita de verificar una variable que se define sería:

[ -v mystr ]
Felix Leipold
fuente
1
No puedo encontrar esto en ninguna de las páginas de manual (para bash o test) pero funciona para mí. ¿Alguna idea de qué versiones se agregó?
Ash Berlin-Taylor
1
Está documentado en Expresiones condicionales , referenciadas por el testoperador en el final de la sección Bourne Shell Builtins . No sé cuándo se agregó, pero no está en la versión de Apple bash(basada en bash3.2.51), por lo que probablemente sea una característica 4.x.
Jonathan Leffler
1
Esto no funciona para mí GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu). El error es -bash: [: -v: unary operator expected. Según un comentario aquí , se requiere como mínimo bash 4.2 para funcionar.
Acumenus
Gracias por comprobar cuándo estuvo disponible. Lo había usado antes en varios sistemas, pero de repente no funcionó. Vine aquí por curiosidad y sí, por supuesto, el bash en este sistema es un 3.x bash.
clacke
1

otra opción: la expansión "listar índices de matriz" :

$ unset foo
$ foo=
$ echo ${!foo[*]}
0
$ foo=bar
$ echo ${!foo[*]}
0
$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

el único momento en que esto se expande a la cadena vacía es cuando no fooestá configurado, por lo que puede verificarlo con la cadena condicional:

$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1
$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

debería estar disponible en cualquier versión bash> = 3.0

Aaron Davies
fuente
1

no arrojar esta bicicleta aún más lejos, pero quería agregar

shopt -s -o nounset

es algo que podría agregar a la parte superior de una secuencia de comandos, lo que generará un error si las variables no se declaran en ninguna parte de la secuencia de comandos. El mensaje que vería es unbound variable, pero como otros mencionan, no detectará una cadena vacía o un valor nulo. Para asegurarnos de que cualquier valor individual no esté vacío, podemos probar una variable a medida que se expande ${mystr:?}, también conocida como expansión de signo de dólar, que generaría un error parameter null or not set.

Sacrílego
fuente
1

El Manual de referencia de Bash es una fuente autorizada de información sobre bash.

Aquí hay un ejemplo de prueba de una variable para ver si existe:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

(De la sección 6.3.2 .)

Tenga en cuenta que el espacio en blanco después de la apertura [y antes de la no] es opcional .


Consejos para usuarios de Vim

Tenía un script que tenía varias declaraciones de la siguiente manera:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

Pero quería que difirieran a cualquier valor existente. Así que los reescribí para que se vean así:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

Pude automatizar esto en vim usando una expresión regular rápida:

s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

Esto se puede aplicar seleccionando visualmente las líneas relevantes y luego escribiendo :. La barra de comandos se rellena previamente con :'<,'>. Pegue el comando anterior y presione enter.


Probado en esta versión de Vim:

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by root@apple.com

Los usuarios de Windows pueden querer diferentes finales de línea.

funroll
fuente
0

Esto es lo que creo que es una forma mucho más clara de verificar si se define una variable:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

Úselo de la siguiente manera:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi
suizo
fuente
1
Escribir una función no es una mala idea; invocar grepes horrible. Tenga en cuenta que bashadmite ${!var_name}como una forma de obtener el valor de una variable cuyo nombre se especifica $var_name, por lo que name=value; value=1; echo ${name} ${!name}rinde value 1.
Jonathan Leffler
0

Una versión más corta para probar una variable indefinida puede ser simplemente:

test -z ${mystr} && echo "mystr is not defined"
nfleury
fuente
-3

conjunto de llamadas sin ningún argumento ... genera todos los vars definidos ...
los últimos en la lista serían los definidos en su script ...
para que pueda canalizar su salida a algo que pueda averiguar qué cosas están definidas y cuáles no

Aditya Mukherji
fuente
2
No voté en contra de esto, pero es una especie de mazo romper una nuez bastante pequeña.
Jonathan Leffler
De hecho, me gusta esta respuesta mejor que la respuesta aceptada. Está claro lo que se está haciendo en esta respuesta. La respuesta aceptada es torpe como el infierno.
Suiza el
Parece que esta respuesta es más un efecto secundario de la implementación de "set" que algo deseable.
Jonathan Morgan el