¿Es la prueba o [o [[más portátil tanto entre los shells de bash como entre otros shells?

44

Veo que puedo hacer

$ [ -w /home/durrantm ] && echo "writable"
writable

o

$ test -w /home/durrantm && echo "writable"
writable

o

$ [[ -w /home/durrantm ]] && echo "writable"
writable

Me gusta usar la tercera sintaxis. ¿Son equivalentes en todos los sentidos y para todos los casos negativos y extremos? ¿Hay alguna diferencia en la portabilidad, por ejemplo, entre bash en Ubuntu y en OS X o versiones anteriores / posteriores de bash, por ejemplo, antes / después de 4.0 y ambas expanden las expresiones de la misma manera?

Michael Durrant
fuente
2
Para [ … ]vs [[ … ]]vs test …, hay respuestas más completas en esta pregunta mayormente duplicada .
Gilles 'SO- deja de ser malvado'
Para el problema específico de las pruebas de escritura de un archivo, consulte también ¿Cómo probar de forma no invasiva el acceso de escritura a un archivo?
G-Man dice 'Restablece a Monica' el
1
Hay un dicho: "no hay código portátil, solo código que ha sido portado". Mi consejo con respecto a esto: use la forma más legible (probablemente [...]]) y pruébelo en todas las plataformas que desee admitir. No hay mucho uso para ocultar sus scripts, por lo que se ejecutan en plataformas antiguas que ni usted ni su público objetivo utilizan. Simplemente hará que su código sea difícil de leer, introducirá errores innecesarios y tal vez incluso problemas de seguridad (como lo hizo para openssl).
stefan.schwetschke

Respuestas:

31

[es sinónimo del testcomando y es simultáneamente un comando bash incorporado y un comando separado. Pero [[es una palabra clave bash y funciona solo en algunas versiones. Entonces, por razones de portabilidad, es mejor usar solo []otest

[ -w "/home/durrantm" ] && echo "writable"
Costas
fuente
2
[¿Está POSIX integrado o Bash integrado?
Sandburg
@Sandburg POSIX es un estándar, por lo que no puede tener incorporados. No define si algo debe incorporarse al intérprete o no, solo lo que tiene que hacer. Estas reglas se aplican a ambas formas, testy [por igual. Si un shell puede hacer la evaluación por sí solo, eso le ahorra un proceso y lo hace algo más rápido, pero el resultado tiene que ser el mismo.
Bachsau
@Bachsau que todavía no responde a la pregunta de Sandburg sobre si el comportamiento de o no [está definido por POSIX y, por lo tanto, es máximamente portátil (que parece ser el punto central de esta pregunta si no me equivoco)
JamesTheAwesomeDude
@Sandburg (y para cualquier otra persona tropezarse con esto): Sí, parece que tanto [y test son POSIX . Parece no haber ninguno "recomendado", aunque la única diferencia (?) Que puedo detectar entre ellos es que test"no reconocerá el argumento" - "[como un delimitador que indica el final de las opciones]" (? - lo que implica que " - " es reconocido como el fin de los argumentos [, para lo cual no puedo encontrar un buen caso de prueba de todos modos. Pero estoy divagando. Usa lo que quieras. OMI, se [ve elegante, pero es testclaramente un comando)
JamesTheAwesomeDude
35

Si, hay diferencias. Los más portátiles son testo [ ]. Ambos son parte de la especificación POSIXtest .

La if ... ficonstrucción también está definida por POSIX y debe ser completamente portátil.

Esta [[ ]]es una kshcaracterística que también está presente en algunas versiones de bash( todas las modernas ), en zshy tal vez en otras, pero no está presente en sho en dashvarios otros shells más simples.

Por lo tanto, para hacer sus guiones portátil, usar [ ], testo if ... fi.

terdon
fuente
10
Solo para tener en cuenta, bashy lo zshhe apoyado [[durante mucho tiempo (lo bashagregué a fines de los 90, a zshmás tardar en el 2000, y me sorprendería si alguna vez careció de soporte), por lo que es poco probable que encuentre una versión de cualquiera de los dos [[. Encontrar un shell diferente compatible con POSIX (como dash) es mucho más probable.
chepner
1
Una característica interesante [[es que no es necesario citar las expansiones de parámetros: el [[-f $file]]evento funciona si $filecontiene caracteres de espacio en blanco.
método de ayuda
2
@helpermethod también, construyendo expresiones regulares[[ $a =~ ^reg.*exp.*$' ]]
GnP
20

Tenga en cuenta que eso [] && cmdno es lo mismo que la if .. ficonstrucción.

A veces su comportamiento es bastante similar y puedes usarlo en [] && cmdlugar de if .. fi. Pero solo a veces. Si tiene más de un comando para ejecutar si la condición o necesita if .. else .. fitener cuidado y cambiar la lógica.

Un par de ejemplos:

[ -z "$VAR" ] && ls file || echo wiiii

No es lo mismo que

if [ -z $VAR ] ; then
  ls file
else
  echo wiii
fi

porque si lsfalla, echose ejecutará, lo que no sucederá con if.

Otro ejemplo:

[ -z "$VAR" ] && ls file && echo wiii

no es lo mismo que

if [ -z "$VAR" ] ; then
   ls file
   echo $wiii
fi

aunque esta construcción actuará igual

[ -z "$VAR" ] && { ls file ; echo wiii ; }

tenga en cuenta que ;después del eco es importante y debe estar allí.

Entonces, resumiendo la declaración anterior, podemos decir

[] && cmd == si el primer comando es exitoso, ejecute el siguiente

if .. fi == si la condición (que también puede ser el comando de prueba), entonces ejecute los comandos

Por lo tanto, para la portabilidad [y el [[uso [solo.

ifes POSIX compatible. Entonces, si tiene que elegir [y ifelegir mirar su tarea y el comportamiento esperado.

prisa
fuente
Probablemente solo estoy siendo denso, pero no veo cuál sería la diferencia ... ¿podría dar un ejemplo donde esas dos construcciones darían resultados diferentes?
evilsoup
1
@evilsoup, actualizado. Puede que no sea el mejor explicador, aunque espero que esté claro ahora.
Rush
1
Muy buenos puntos, prisa. Supongo que una forma segura de su primera ejemplo sería: [ -z "$VAR" ] && { ls file; true; } || echo wiiii. Es un poco más detallado, pero aún es más corto que la if...ficonstrucción.
PM 2Ring
La pregunta fue sobre [vs [[vs test y no también sobre si ... fi vs && para que sea UNA pregunta. Desafortunadamente hacer eso hace que esta respuesta parezca fuera de lugar. Disculpas por no obtener la pregunta más centrada inicialmente que condujo a esto. Vive y (intenta) aprender. :)
Michael Durrant
Voté en contra porque a) esta es principalmente una buena respuesta, pero es una respuesta a una pregunta diferente. b) usted presente [y ifcomo substitutos, pero no lo son. En realidad, es lo &&que está reemplazando al if. [ejecuta un código y devuelve un estado, al igual que lsy grepwould. ifLa ejecución de las ramas depende del estado de retorno del comando (instrucción) dado después del if, podría ser cualquier comando (instrucción). &&ejecuta la siguiente declaración solo si la anterior devolvió 0, de forma muy similar a una simple if..then..fi.
GnP
9

En realidad, &&es lo que está reemplazando el if, no el test: una ifdeclaración en las secuencias de comandos de shell prueba si un comando devolvió un estado de salida "exitoso" (cero); en su ejemplo, el comando es [.

Entonces, en realidad hay dos cosas que está variando aquí: el comando utilizado para ejecutar la prueba y la sintaxis utilizada para ejecutar el código en función del resultado de esa prueba.

Comandos de prueba:

  • testes un comando estandarizado para evaluar las propiedades de cadenas y archivos; en su ejemplo, está ejecutando el comandotest -w /home/durrantm
  • [es un alias de ese comando, igualmente estandarizado, que tiene un último argumento obligatorio ]para parecer una expresión entre corchetes; no se deje engañar, sigue siendo solo un comando (incluso puede encontrar que su sistema tiene un archivo llamado /bin/[)
  • [[es una versión extendida del comando de prueba integrado en algunos shells, pero no forma parte del mismo estándar POSIX; incluye opciones adicionales que no estás usando aquí

Expresiones condicionales:

  • El &&operador ( estandarizado aquí ) realiza una operación AND lógica, evaluando dos comandos y devolviendo 0 (que representa verdadero) si ambos devuelven 0; solo evaluará el segundo comando si el primero devuelve cero, por lo que puede usarse como un simple condicional
  • El if ... then ... ficonstructo ( estandarizado aquí ) utiliza el mismo método para juzgar la "verdad", pero permite una lista compuesta de declaraciones en la thencláusula, en lugar del comando único proporcionado por un &&cortocircuito, y proporciona elify elsecláusulas, que son difíciles de escribir utilizando solo &&y ||. Tenga en cuenta que no hay corchetes alrededor de la condición en una ifdeclaración .

Por lo tanto, las siguientes son representaciones de su ejemplo igualmente portátiles y totalmente equivalentes:

  • test -w /home/durrantm && echo "writable"
  • [ -w /home/durrantm ] && echo "writable"
  • if test -w /home/durrantm; then echo "writable"; fi
  • if [ -w /home/durrantm ]; then echo "writable"; fi

Si bien los siguientes también son equivalentes, pero menos portátiles debido a la naturaleza no estándar de [[:

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi
IMSoP
fuente
Sí, eliminé la parte if ... fi para que sea una pregunta.
Michael Durrant
7

Para portabilidad, use test/ [. Pero si no necesita la portabilidad, por el bien de usted mismo y de otros que leen su script, use [[. :)

Ver también What is the difference between test, [ and [[ ?en BashFAQ .

PM 2Ring
fuente
7

Si desea portabilidad fuera del mundo tipo Bourne, entonces:

test -w /home/durrantm && echo writable

Es el más portátil. Funciona en conchas del Bourne, cshy rcfamilias.

test -w /home/durrantm && echo "writable"

salida sería "writable"en lugar de writableen los depósitos de la rcfamilia ( rc, es, akanga, donde "no es especial).

[ -w /home/durrantm ] && echo writable

no funcionaría en shells de csho rcfamilias en sistemas que no tienen un [comando $PATH(se sabe que algunos tienen testpero no su [alias).

if [ -w /home/durrantm ]; then echo writabe; fi

solo funciona en conchas de la familia Bourne.

[[ -w /home/durrantm ]] && echo writable

solo funciona en ksh(donde se originó) zshy bash(los 3 en la familia Bourne).

Ninguno funcionaría en el fishshell donde necesita:

[ -w /home/durrantm ]; and echo writable

o:

if [ -w /home/durrantm ]; echo writable; end
Stéphane Chazelas
fuente
0

Mi razón más importante para elegir if foo; then bar; fio foo && bares si el estado de salida de todo el comando es importante.

comparar:

#!/bin/sh
set -e
foo && bar
do_baz

con:

#!/bin/sh
set -e
if foo; then bar; fi
do_baz

Se podría pensar que hacen lo mismo; sin embargo, si foofalla (o es falso, dependiendo de su punto de vista), en el primer ejemplo, do_baz no se ejecutará, ya que el script habrá salido ... Le set -eindica al shell que salga inmediatamente si algún comando devuelve un estado falso. Muy útil si estás haciendo cosas como:

cd /some/directory
rm -rf *

No desea que el script continúe ejecutándose si cdfalla por alguna razón.

wurtel
fuente
1
No, una falla foono abortará el script en ninguno de los casos. Eso es un caso especial para set -e(cuando el comando se evalúa como una condición (a la izquierda de algunos && / || o si /, mientras que / hasta / elsif ... condiciones) Una falla. barPodría salir de la cáscara en ambos casos.
Stéphane Chazelas
2
Desea cd /some/directory && rm -rf -- *o cd /some/directory || exit; rm -rf -- *(aún no elimina los archivos ocultos). Personalmente no me gusta la idea de usar set -ecomo excusa para no hacer un esfuerzo para escribir el código correcto.
Stéphane Chazelas