Por que o ramo 'if [$ 1 = “1”]' sempre é selecionado, mesmo que $ 1 não seja 1?

10

Eu tenho um script de shell chamado 'teleport.sh' assim:

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

Quando executo:

sh teleport.sh 2 testfile

Isso testfileé movido para o ~/lab/Sundiretório, o que me confunde muito porque eu não passei 1 ou '1' para esse script.

O que há de errado aqui?

zen
fonte
1
+1 para laboratório , Sol , Lua , Terra e teleporte . Mas você deve sempre aspas duplas expansões ( $var, $(cmd)e até mesmo `cmd`[para o qual $(cmd)deve ser preferido]). Existem alguns casos extremos em que você não precisa citar, mas sempre isso não fará mal.
usar o seguinte código
@ nyuszika7h, as aspas duplas não devem significar "$ var" e "$ cmd"? qual é o benefício do suporte redondo que você mencionou acima?
Zen
$(cmd)é substituição de comando , (principalmente) o mesmo que `cmd`. Veja mywiki.wooledge.org/CommandSubstitution e mywiki.wooledge.org/BashFAQ/082
nyuszika7h

Respostas:

19

O uso de espaços corrige seu 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

Embora isso seja mais puro:

#!/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
fonte
3
Sim, os espaços são necessários, mas estou me perguntando por que a $1="1"sintaxe original "funciona" (produzindo um resultado verdadeiro). Como o [/ testbuiltin realmente interpreta essa expressão?
echristopherson
5
@echristopherson Sem espaços, testvê apenas um argumento (um "valor l"). Sem os outros argumentos (um "operador" e um "valor-r"), não há nada a testar, então testapenas diz "ok, bem, sim, você me deu algo e isso deve ser vacuamente verdadeiro".
bispo
2
$1="1"é $1concatenado por=1
jdh8
ao usar o caso, como definir uma ação a ser executada quando nenhuma das condições for atendida?
Zen
11

A primeira coisa óbvia é que você deve fornecer espaços entre os argumentos de [, testou [[:

if [ "$1" = 1 ];

No Bash, o uso [[ ]]é recomendado, pois não faz coisas desnecessárias para a expressão condicional, como divisão de palavras e expansão do nome do caminho. Também não é necessário citar aspas duplas. Um operador mais legível ==também pode ser usado.

if [[ $1 == 1 ]];

Nota acrescentou: Se segundo operando também contém variáveis, citando é necessária, pois podem estar sujeitos a correspondência de padrão se ele contém personagens reconhecíveis como *, ?, [], etc .. Se estendida englobamento ou correspondência padrão é ativado com shopt -s extglob, outras formas como @(), !(), etc. também será reconhecido como padrões. Consulte Correspondência de padrões .

Com operadores como <e >ainda pode ser necessário, pois uma vez encontrei um bug em que não citar o segundo argumento causou resultados diferentes.

Quanto ao primeiro operando, nada se aplica.

Considere também essa variação mais simples:

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

Ou condensado:

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

"${@:2}"é uma forma de expansão de substring ou expansão de membro da matriz onde 2está o deslocamento. Isso faz com que a expansão comece com o segundo valor. Com isso, talvez não precisemos usar shift.

O --item adicionado impede o mvreconhecimento de nomes de arquivos iniciados por dash ( -) como opções inválidas.

konsolebox
fonte
você não deveria quebrar em cada caso?
Archemar 8/08/14
2
@Archemar: não, não há falhas (ao contrário de muitas outras línguas).
Mat
2
@ Mat, há falhas se você usar em ;&vez de ;;(apenas ksh, bash, zsh). Mas, breakainda assim, não impede a queda, breaké apenas romper os loops.
Stéphane Chazelas
Isso não é argumentos para, ifmas para o [comando.
Stéphane Chazelas
1
Correção: aspas duplas não são necessárias apenas no lado esquerdo! [[ $foo == $bar ]]fará a correspondência de padrões, mas [[ $foo == "$bar" ]]não fará.
usar o seguinte código
7

Para responder à pergunta de por que isso está acontecendo, esse comportamento de [aka testestá documentado no POSIX :

Na lista a seguir, $ 1, $ 2, $ 3 e $ 4 representam os argumentos apresentados para testar:

[...]

1 argumento:

Sair verdadeiro (0) se $ 1 não for nulo; caso contrário, saia falso.

Você está passando um argumento, 2=1que não é nulo e, portanto, testsai com sucesso.

Como outras postagens (e verificação de shell ) apontam, se você quiser comparar a igualdade, terá que passar os 3 argumentos 2, =e 1.

aquele outro cara
fonte
3
Em certo sentido, essa é a única resposta que respondeu à pergunta (em vez de fornecer uma ou mais receitas que fazem o que o OP queria realizar), e o shell pode ser suficientemente misterioso para saber por que ele faz as coisas que faz. .
dmckee --- ex-moderador gatinho
1

Eu só quero recomendar uma alternativa portátil, mas também mais limpa. O Bash não é universal (e se você não precisa de universal, por que está escrevendo um shell script?)

#! /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"

(Observação para os pedantes: sim, eu sei que as citações $actionna case "$action" inlinha são desnecessárias, mas acho que é melhor colocá-las lá de qualquer maneira, para que futuros leitores não precisem se lembrar disso.)

zwol
fonte
1
"O Bash não é universal (e se você não precisa de universal, por que está escrevendo um script de shell?)" - isso parece implicar que o script bash não possui casos de uso.
Ruslan #
@Ruslan Sim, essa é a minha opinião. Escreva /bin/shscripts portáteis , se precisar; caso contrário, escreva em uma linguagem de script menos terrível que o shell. De fato, o intérprete básico de Perl tem maior probabilidade de estar presente em ambientes proprietários herdados e em ambientes incorporados reduzidos do que o Bash.
Zwol 9/08