Por que a ramificação 'if [$ 1 = “1”]' sempre é selecionada, mesmo que $ 1 não seja 1?

9

Eu tenho um script 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

Esse testfile é movido para o diretório ~/lab/Sun , o que me confunde muito, já que não passei 1 ou '1' para esse script.

O que há de errado aqui?

    
por Zen 08.08.2014 / 13:08

4 respostas

17

O uso de espaços resolve 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 simples:

#!/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
    
por 08.08.2014 / 13:11
11

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

if [ "$1" = 1 ];

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

if [[ $1 == 1 ]];

Nota adicionada: Se o segundo operando também contiver variáveis, é necessário cotar, pois ele pode estar sujeito à correspondência de padrões se contiver caracteres reconhecíveis como * , ? , [] , etc. A correspondência está ativada com shopt -s extglob , outras formas como @() , !() , etc. também serão reconhecidas como padrões. Veja Correspondência de padrões .

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

Quanto ao primeiro operando, nada se aplica.

Considere também esta 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 de matriz em que 2 é o deslocamento. Isso faz com que a expansão comece no segundo valor. Com isso, talvez não seja necessário usar shift .

O -- adicionado impede que mv reconheça nomes de arquivos começando com traço ( - ) como opções inválidas.

    
por 08.08.2014 / 13:11
6

Para responder à pergunta de por que isso está acontecendo, esse comportamento de [ aka test é documentado em POSIX :

In the following list, $1, $2, $3, and $4 represent the arguments presented to test:

[...]

1 argument:

Exit true (0) if $1 is not null; otherwise, exit false.

Você está transmitindo 1 argumento, 2=1 , que não é nulo e, portanto, test sai com êxito.

Como outros posts (e shellcheck ) apontam, se você quisesse comparar por igualdade, você teria que passar os 3 argumentos 2 , = e 1 .

    
por 09.08.2014 / 01:23
1

Eu só quero recomendar uma alternativa portátil e também mais pura. Bash é não universal (e se você não precisa de universal, por que você está escrevendo um 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 aos pedantes: sim, eu sei que as aspas em torno de $action na linha case "$action" in são desnecessárias, mas eu sinto que é melhor colocá-las lá de qualquer maneira, para que os futuros leitores não tenham que lembrar .)

    
por 08.08.2014 / 18:26

Tags