Dlaczego gałąź „if [$ 1 =„ 1 ”]” jest zawsze wybierana, nawet jeśli 1 $ nie jest równa 1?

10

Mam skrypt powłoki o nazwie „teleport.sh” w następujący sposób:

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

Kiedy wykonam:

sh teleport.sh 2 testfile

Zostało testfileto przeniesione do ~/lab/Sunkatalogu, co mnie bardzo myli, ponieważ nie przekazałem 1 lub 1 do tego skryptu.

Co tu jest nie tak?

Zen
źródło
1
+1 za laboratorium , Słońce , Księżyc , Ziemię i teleportację . Ale zawsze powinieneś podwoić cudzysłowy ( $var, $(cmd)a nawet `cmd`[do których $(cmd)należy preferować]). Są przypadki skrajne, w których nie musisz cytować, ale zawsze robienie tego nie zaszkodzi.
nyuszika7h
@ nyuszika7h, czy podwójny cytat nie powinien oznaczać „$ var” i „$ cmd”? jaka jest korzyść z okrągłego wspornika, o którym wspomniałeś powyżej?
Zen,
$(cmd)to podstawianie poleceń (w większości) to samo co `cmd`. Zobacz mywiki.wooledge.org/CommandSubstitution i mywiki.wooledge.org/BashFAQ/082
nyuszika7h

Odpowiedzi:

19

Używanie spacji rozwiązuje problem.

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

Chociaż jest to ładniejsze:

#!/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
źródło
3
Tak, spacje są konieczne, ale zastanawiam się, dlaczego oryginalna $1="1"składnia w ogóle „działa” (dając prawdziwy wynik). W jaki sposób wbudowane [/ testinterpretuje to wyrażenie?
echristopherson
5
@echristopherson Bez spacji testwidzi tylko jeden argument („wartość l”). Bez innych argumentów („operator” i „wartość r”) nie ma nic do przetestowania, więc testpo prostu mówi „ok, cóż, tak, dałeś mi coś i to musi być szczerze prawda”.
biskup
2
$1="1"jest $1konkatenowany przez=1
jdh8
w przypadku użycia przypadku, jak ustawić akcję, która ma być wykonywana, gdy żaden z warunków nie jest spełniony?
Zen,
11

Pierwszy oczywistą rzeczą jest należy podać spacji pomiędzy argumentami [, testczy [[:

if [ "$1" = 1 ];

W Bash [[ ]]zaleca się używanie, ponieważ nie robi rzeczy niepotrzebnych dla wyrażeń warunkowych, takich jak dzielenie słów i rozwijanie nazw ścieżek. Cytowanie wokół podwójnych cudzysłowów również nie jest potrzebne. ==Można również użyć bardziej czytelnego operatora .

if [[ $1 == 1 ]];

Dodano Uwaga: Jeśli drugi argument zawiera również zmienne, powołując jest konieczne, ponieważ może to być przedmiotem wyszukiwania wzorca, jeśli zawiera rozpoznawalne znaki takie jak *, ?, []itp .. Jeśli rozszerzony globbing lub pasujące do wzorca jest włączona shopt -s extglob, inne formy podoba @(), !()itp będą również rozpoznawane jako wzory. Zobacz Dopasowywanie wzorów .

Z operatorami podobnymi <i >może być to nadal konieczne, ponieważ kiedyś napotkałem błąd, w którym brak podania drugiego argumentu spowodował różne wyniki.

Jeśli chodzi o pierwszy operand, nic nie ma zastosowania.

Rozważ również tę prostszą odmianę:

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

Lub skondensowane:

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

"${@:2}"jest formą rozszerzenia podłańcucha lub rozszerzenia elementu tablicy, gdzie 2jest przesunięcie. To powoduje, że ekspansja rozpoczyna się od drugiej wartości. Dzięki temu możemy nie musieć korzystać shift.

Dodane --zapobiega mvrozpoznawaniu nazw plików rozpoczynających się od dash ( -) jako niepoprawne opcje.

konsolebox
źródło
nie powinieneś łamać się w każdym przypadku?
Archemar
2
@Archemar: nie, nie ma przewrotu (w przeciwieństwie do wielu innych języków).
Mat
2
@Mat, istnieje awaria, jeśli użyjesz ;&zamiast ;;(tylko ksh, bash, zsh). Ale wtedy breaknadal nie zapobiega upadkowi, breakwystarczy wyrwać się z pętli.
Stéphane Chazelas
To nie są argumenty, ifale [polecenie.
Stéphane Chazelas
1
Korekta: podwójne cudzysłowy nie są potrzebne tylko po lewej stronie! [[ $foo == $bar ]]wykona dopasowanie wzorca, ale [[ $foo == "$bar" ]]nie zrobi tego.
nyuszika7h,
7

Aby odpowiedzieć na pytanie, dlaczego tak się dzieje, takie zachowanie [aka testjest udokumentowane w POSIX :

Na poniższej liście 1 $, 2 $, 3 $ i 4 $ reprezentują argumenty przedstawione do przetestowania:

[...]

1 argument:

Wyjdź z wartości true (0), jeśli $ 1 nie ma wartości zerowej; w przeciwnym razie zamknij false.

Podajesz mu 1 argument, 2=1który nie jest zerowy i dlatego testkończy się sukcesem.

Innych stanowisk (i shellcheck ) zwracają uwagę, jeśli chcesz porównać dla równości, byś zamiast musiał przejść 3 argumenty 2, =i 1.

ten inny facet
źródło
3
W pewnym sensie jest to jedyna odpowiedź, która odpowiedziała na zadane pytanie (zamiast podawać jeden lub więcej przepisów, które robią to, co PO chciał osiągnąć), a powłoka może być wystarczająco tajemnicza, aby wiedzieć, dlaczego robi to, co robi, jest przydatna .
dmckee --- były moderator kociak
1

Chciałbym tylko polecić przenośną, ale także ładniejszą alternatywę. Bash nie jest uniwersalny (a jeśli nie potrzebujesz uniwersalnego, dlaczego piszesz skrypt powłoki?)

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

(Uwaga dla pedantów: tak, wiem, że cytaty $actionw case "$action" inwierszu są niepotrzebne, ale uważam, że najlepiej jest je tam umieścić, aby przyszli czytelnicy nie musieli o tym pamiętać.)

zwol
źródło
1
„Bash nie jest uniwersalny (a jeśli nie potrzebujesz uniwersalnego, dlaczego piszesz skrypt powłoki?)” - wydaje się to sugerować, że skrypty bash nie mają przypadków użycia.
Ruslan,
@Ruslan Tak, to moja przemyślana opinia. Napisz przenośne /bin/shskrypty, jeśli potrzebujesz; w przeciwnym razie pisz w języku skryptowym, który jest mniej straszny niż shell. Podstawowy interpreter Perla jest bardziej prawdopodobne, że będzie obecny w starszych, zastrzeżonych środowiskach i odciętych środowiskach wbudowanych niż Bash.
zwolnienie