¿Por qué la comprobación de si una carpeta existe usando -d en una cadena vacía devuelve verdadero?


8

Estaba escribiendo algunos guiones y escribí algo como

ARTIFACTS="/SOME/PATH"
[ -d $ARTIFCATS ] && rm -rf $ARTIFACTS/*

Lo que sucedió es que por estupidez ejecuté la segunda línea sin ejecutar la primera. Resultó que [-d ""] devuelve verdadero y la expresión se convirtió en

rm -rf /*

Afortunadamente era solo una máquina de prueba y no era un sudo, pero aunque perdí algunos datos

Mi pregunta es, ¿por qué [-d ""] devuelve verdadero? la documentación indica claramente que verifica si existe una ruta y si es una carpeta

Resolví el problema usando

[ -e $ARTIFACTS ]
que parece funcionar

Salud


55
O tal vez ejecutaste ambas líneas. En el ejemplo de código anterior, nunca configuras ARTIFCATS.
Buhb

2
Simplemente escribiría eso rm -rf $ARTIFACTSsin el /*. Esto también eliminaría el $ARTIFACTSdirectorio, lo cual está bien, porque si quiero estar seguro de que existe antes de poner algo en él, lo ejecutaré de mkdir -p $ARTIFACTStodos modos. También eliminará los archivos ocultos dentro $ARTIFACTS, lo cual también está bien, porque no escribiría rm -rf $ARTIFACTS/*si $ARTIFACTScontuviera algo que quisiera guardar.
Christoffer Hammarström

@ ChristofferHammarström muy cierto
Moataz Elmasry

Respuestas:


9

1. Estas dos pruebas devuelven verdadero :

# [ -d ] && echo true || echo false
true
# [ -d $SOME_UNSET_VAR ] && echo true || echo false
true

de acuerdo con POSIX (como lo explica @Tim).

2. Pero esto devuelve falso ( no es cierto como se indica en la pregunta)

# [ -d "" ] && echo true || echo false
false

porque testse llama con dos argumentos (aunque el segundo es una cadena vacía).

3. Es por eso que es una buena práctica usar en [[ … ]]lugar de test( [ … ]), que proporcionan la mayoría (¿todos?) De los shells actuales. Esta construcción verifica si proporciona suficientes argumentos (de lo contrario, arroja un error y aborta)

# [[ -d ]] && echo true || echo false
bash: unexpected argument `]]' to conditional unary operator
bash: syntax error near `]]'

o simplemente se comporta como uno esperaría:

# [[ -d $SOME_UNSET_VAR ]] && echo true || echo false
false

4. Y, como señaló @Gilles, aún más importante es duplicar las sustituciones. Por lo tanto, se -d "$SOME_UNSET_VAR"expande -d ""y devuelve falso incluso con test(igual al caso 2). Por lo tanto, esto también es compatible con el shell Bourne sh:

# [ -d "$SOME_UNSET_VAR" ] && echo true || echo false
false

probado con bash 3.00.16 (1) y 4.1.5 (1)


1
Bash, ksh y zsh proporcionan [[ … ]]pero no son simples sh. La práctica realmente importante es poner comillas dobles sustituciones de comando : [ -d "$ARTIFACTS" ].
Gilles 'SO- deja de ser malvado'

6

Esta pregunta ya ha sido respondida en StackOverflow. Dice que de acuerdo con el estándar POSIX, testsiempre debe devolverse con éxito si se llama con exactamente un argumento no vacío (y sin otros argumentos).

Este también debería ser el caso con test -e(y de hecho está en mi sistema), así que tenga cuidado.

En su lugar use:

[ -d "$ARTIFACTS" ]

test luego se llamará con dos argumentos incluso si la variable está vacía y devuelve falso en este caso.


"exactamente un argumento no vacío". - este es un resumen engañoso: la página que ha vinculado menciona que se llamó con exactamente un argumento y que ese argumento no está vacío; nada sobre el caso de un argumento no vacío seguido de uno vacío. La solución correcta debería ser rodear el nombre de la variable entre comillas.
Random832

@ Random832 ¡Gracias! Implementé ambos cambios sugeridos.
Tim

[ ! -z $ARTIFACTS ] && [ -d $ARTIFACTS ]no es mejor: solo atiende el caso particular cuando $ARTIFACTSestá vacío, pero falla cuando $ARTIFACTSpuede contener espacios en blanco o \[?*. [ -d "$ARTIFACTS" ]es la forma correcta de hacerlo (o [[ -d $ARTIFACTS ]]en shells que lo tienen [[ … ]]).
Gilles 'SO- deja de ser malvado'

@Gilles Tienes razón, lo eliminé. ¡Gracias!
Tim

4

Resolví el problema usando [-e $ ARTIFACTS] que parece funcionar

Estás equivocado . Funciona porque $ARTIFACTSahora está configurado en algo.

Cuando no se establece una variable, entonces decir

[ -d $SOMEVAR ]

o

[ -e $SOMEVAR ]

sería tanto evaluar a trueporque implica diciendo

[ -d ]

y

[ -e ]

respectivamente. (Decir [ foobar ]siempre se evaluaría como verdadero).

Diciendo

set -u

resulta útil en tales situaciones. help sette diría:

  -u  Treat unset variables as an error when substituting.

[-e ""] && echo "IMPRIMIR ALGO" no imprime nada, entonces, ¿cómo puede estar mal?
Moataz Elmasry

2
ahh mierda [-e $ ARTEFACTOS] con artefactos vacíos produce [-e] no [-e ""], lo tengo
Moataz Elmasry

4

Vea que configuró la variable ARTIFACTS y que estaba buscando ARTIFCATS. ¿Probablemente escribiendo mal?

De todos modos, -d así como -e producirían los mismos resultados en variables no establecidas.

Por lo tanto, use comillas dobles y le ayudará.

ARTIFACTS="/SOME/PATH"
[ -d "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS/"*

NOTA: Si su "/ SOME / PATH" tiene alguna carpeta con espacio, el script que mencionó se romperá con el error "operador binario esperado".

Ejemplo:

ARTIFACTS="/home backup/"

1) [ -d $ARTIFACTS ] && rm -rf $ARTIFACTS/*
bash: [: /home: binary operator expected

2) [ -d "$ARTIFACTS" ] && rm -rf "$ARTIFACTS"/*

Lo haré bien. No olvide poner también comillas en la rminvocación ( rm -rf $ARTIFACTSeliminaría alegremente y /homeluego se quejaría por backup/*no existir).

Además, incluir la -Lverificación se asegurará de que sea un directorio y no solo un enlace simbólico a un directorio.

Así que básicamente,

[ -d "$ARTIFACTS" && ! -L "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS"/*
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.