¿Por qué la rama 'if [$ 1 = “1”]' siempre se selecciona incluso si $ 1 no es 1?

10

Tengo un script de shell llamado 'teleport.sh' como este:

if [ $1="1" ];
    then
    shift
        mv "$@" ~/lab/Sun
elif [ $1="2" ];
    then
    shift
        mv "$@" ~/lab/Moon
elif [ $1="3" ];
    then
    shift
        mv "$@" ~/lab/Earth
fi

Cuando ejecuto:

sh teleport.sh 2 testfile

Esto testfilese mueve al ~/lab/Sundirectorio, lo que me confunde mucho ya que no pasé 1 o '1' a ese script.

¿Qué pasa aquí?

zen
fuente
1
+1 para laboratorio , sol , luna , tierra y teletransporte . Sin embargo, usted debe siempre dobles comillas expansiones ( $var, $(cmd)e incluso `cmd`[a la que $(cmd)se debe preferir]). Hay algunos casos extremos en los que no tiene que citar, pero hacerlo siempre no le hará daño.
nyuszika7h
@ nyuszika7h, ¿no debería la comilla doble significa "$ var" y "$ cmd"? ¿Cuál es el beneficio del soporte redondo que ha mencionado anteriormente?
Zen
$(cmd)es la sustitución de comandos , (principalmente) lo mismo que `cmd`. Ver mywiki.wooledge.org/CommandSubstitution y mywiki.wooledge.org/BashFAQ/082
nyuszika7h

Respuestas:

19

Usar espacios soluciona tu problema.

if [ "$1" = 1 ];
    then
    shift
        mv "$@" ~/lab/Sun
elif [ "$1" = 2 ];
    then
    shift
        mv "$@" ~/lab/Moon
elif [ "$1" = 3 ];
    then
    shift
        mv "$@" ~/lab/Earth
fi

Aunque esto es más ordenado:

#!/bin/bash

action=$1
shift
files=("$@")
case $action in  
  1) mv -- "${files[@]}" ~/lab/Sun     ;;
  2) mv -- "${files[@]}" ~/lab/Moon    ;;
  3) mv -- "${files[@]}" ~/lab/Earth   ;;
esac
Karlo
fuente
3
Sí, los espacios son necesarios, pero me pregunto por qué la $1="1"sintaxis original "funciona" (produciendo un resultado verdadero). ¿Cómo interpreta realmente [/ testbuiltin esa expresión?
echristopherson
55
@echristopherson Sin espacios, testsolo ve un argumento (un "valor l"). Sin los otros argumentos (un "operador" y un "valor r"), no hay nada que probar, así que testsolo dice "ok, bueno sí, me diste algo y eso debe ser completamente cierto".
obispo
2
$1="1"es $1concatenado por=1
jdh8
cuando se usa mayúsculas y minúsculas, ¿cómo establecer una acción para ejecutar cuando ninguna de las condiciones se cumple?
Zen
11

La primera cosa obvia es que debe proporcionar espacios entre los argumentos de [, testo [[:

if [ "$1" = 1 ];

Cuando está en Bash, [[ ]]se recomienda el uso, ya que no hace cosas innecesarias para la expresión condicional, como la división de palabras y la expansión de nombres de ruta. Citar alrededor de comillas dobles tampoco es necesario. ==También se puede utilizar un operador más legible .

if [[ $1 == 1 ]];

Se agregó una nota: Si segundo operando también contiene variables, citando es necesario, ya que puede estar sujeta a la coincidencia de patrones si contiene caracteres reconocibles como *, ?, [], etc .. Si englobamiento extendido o coincidencia de patrones está habilitado con shopt -s extglob, otras formas como @(), !(), etc. También será reconocido como patrones. Ver coincidencia de patrones .

Con operadores similares <y >todavía puede ser necesario, ya que una vez me encontré con un error en el que no citar el segundo argumento causaba resultados diferentes.

En cuanto al primer operando, no se aplica nada.

Considere también esta variación más simple:

case "$1" in
1)
    mv -- "${@:2}" ~/lab/Sun
    ;;
2)
    mv -- "${@:2}" ~/lab/Moon
    ;;
3)
    mv -- "${@:2}" ~/lab/Earth
    ;;
esac

O condensado:

case "$1" in
1) mv -- "${@:2}" ~/lab/Sun ;;
2) mv -- "${@:2}" ~/lab/Moon ;;
3) mv -- "${@:2}" ~/lab/Earth ;;
esac

"${@:2}"es una forma de expansión de subcadena o expansión de miembro de matriz donde 2está el desplazamiento. Esto hace que la expansión comience en el segundo valor. Con esto puede que no necesitemos usar shift.

El agregado --impide mvreconocer nombres de archivo que comienzan con guión ( -) como opciones no válidas.

konsolebox
fuente
¿No deberías romper en cada caso?
Archemar
2
@Archemar: no, no hay fallos (al contrario de muchos otros idiomas).
Mat
2
@ Mat, hay fallos si se usa en ;&lugar de ;;(ksh, bash, zsh solamente). Pero breakaún así no evita la caída, breakes solo salir de los bucles.
Stéphane Chazelas
Eso no son argumentos ifsino para el [comando.
Stéphane Chazelas
1
Corrección: ¡las comillas dobles no son necesarias en el lado izquierdo! [[ $foo == $bar ]]realizará la coincidencia de patrones, pero [[ $foo == "$bar" ]]no lo hará.
nyuszika7h
7

Para responder a la pregunta de por qué sucede esto, este comportamiento de [aka testestá documentado en POSIX :

En la siguiente lista, $ 1, $ 2, $ 3 y $ 4 representan los argumentos presentados para probar:

[...]

1 argumento:

Salga verdadero (0) si $ 1 no es nulo; de lo contrario, salga falso.

Le está pasando 1 argumento, 2=1que no es nulo y, por lo tanto, testsale con éxito.

Como señalan otras publicaciones (y shellcheck ), si quisiera comparar por igualdad, en su lugar tendría que pasar los 3 argumentos 2, =y 1.

ese otro chico
fuente
3
En cierto sentido, esta es la única respuesta que ha respondido a la pregunta formulada (en lugar de dar una o más recetas que hacen lo que el OP quería lograr), y Shell puede ser lo suficientemente misterioso como para saber por qué hace las cosas que hace es útil. .
dmckee --- ex gatito moderador el
1

Solo quiero recomendar una alternativa portátil pero también más ordenada. Bash no es universal (y si no necesita universal, ¿por qué está escribiendo un script de shell?)

#! /bin/sh
action="$1"
shift
case "$action" in
    1) dest=Sun   ;;
    2) dest=Moon  ;;
    3) dest=Earth ;;
    *) echo "Unrecognized action code '$action' (must be 1, 2, or 3)" >&2; exit 1 ;;
esac
mv -- "$@" ~/lab/"$dest"

(Nota para los pedantes: sí, sé que las citas $actionen la case "$action" inlínea son innecesarias, pero creo que es mejor ponerlas allí de todos modos, para que los futuros lectores no tengan que recordar eso).

zwol
fuente
1
"Bash no es universal (y si no necesitas universal, ¿por qué estás escribiendo un script de shell?"), Esto parece implicar que las secuencias de comandos bash no tienen casos de uso.
Ruslan
@Ruslan Sí, esa es mi opinión considerada. Escriba /bin/shscripts portátiles , si lo necesita; de lo contrario, escriba en un lenguaje de script que sea menos terrible que el shell. De hecho, es más probable que el intérprete básico de Perl esté presente en entornos propietarios heredados y entornos integrados reducidos que Bash.
zwol