Почему ветвь 'if [$ 1 = “1”] ”всегда выбирается, даже если $ 1 не равен 1?

10

У меня есть сценарий оболочки с именем «teleport.sh», как это:

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

Когда я выполню:

sh teleport.sh 2 testfile

Это testfileперемещено в ~/lab/Sunкаталог, что меня очень смущает, так как я не передал 1 или '1' в этот скрипт.

Что здесь не так?

Zen
источник
1
+1 для лаборатории , Солнца , Луны , Земли и телепорта . Но вы всегда должны использовать двойные кавычки ( $var, $(cmd)и даже `cmd`[которым $(cmd)следует отдавать предпочтение]). Есть некоторые крайние случаи, когда вам не нужно цитировать, но всегда делать это не повредит.
nyuszika7h
@ nyuszika7h, разве двойные кавычки не должны означать "$ var" и "$ cmd"? какая польза от круглой скобки, которую вы упомянули выше?
Дзен
$(cmd)это команда подстановки , ( в основном) такой же , как `cmd`. См. Mywiki.wooledge.org/CommandSubstitution и mywiki.wooledge.org/BashFAQ/082
nyuszika7h

Ответы:

19

Использование пробелов решает вашу проблему.

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

Хотя это аккуратнее

#!/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
источник
3
Да, пробелы необходимы, но мне интересно, почему оригинальный $1="1"синтаксис "работает" вообще (дает истинный результат). Как [/ testвстроенная на самом деле интерпретирует это выражение?
echristopherson
5
@echristopherson Без пробелов testвидит только один аргумент («l-значение»). Без других аргументов («оператор» и «r-значение») проверять нечего, поэтому testпросто говорит: «Хорошо, да, вы дали мне кое-что, и это должно быть бессмысленно верно».
епископ
2
$1="1"это $1сцепляются по=1
jdh8
при использовании case, как установить действие для выполнения, когда не выполняется ни одно из условий?
Дзен
11

Во- первых очевидно , что это вы должны обеспечить пробелы между аргументами [, testили [[:

if [ "$1" = 1 ];

В Bash [[ ]]рекомендуется использовать, так как он не делает ненужных вещей для условного выражения, таких как разбиение слов и расширение пути. Цитировать вокруг двойных кавычек тоже не нужно. Также ==может быть использован более читаемый оператор .

if [[ $1 == 1 ]];

Добавлено примечание: Если второй операнд также содержит переменные, цитирует необходимо , так как это может быть при условии сопоставления с образцом , если он содержит узнаваемые символы , такие как *, ?, []и т.д .. Если расширенная подстановки или соответствующий шаблон включен shopt -s extglob, другие формы , как @(), !()и т.д. также будет распознаваться как шаблоны. Смотрите Pattern Matching .

С такими операторами, как <и >это, все еще может быть необходимо, поскольку я однажды столкнулся с ошибкой, при которой не цитирование второго аргумента приводило к другим результатам.

Что касается первого операнда, ничего не применяется.

Рассмотрим и этот более простой вариант:

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

Или сжатый:

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

"${@:2}"является формой расширения подстроки или расширения элемента массива, где 2это смещение. Это заставляет расширение начинаться со второго значения. С этим нам, возможно, не понадобится пользоваться shift.

Добавленное --предотвращает mvраспознавание имен файлов, начинающихся с dash ( -), как недопустимых параметров.

konsolebox
источник
ты не должен ломаться в каждом случае?
Архемар
2
@Archemar: нет, здесь нет провалов (в отличие от многих других языков).
Мат
2
@ Мат, есть провал, если вы используете ;&вместо ;;(только ksh, bash, zsh). Но тогда breakвсе равно не препятствует провалу, breakостается только вырваться из петель.
Стефан Шазелас
Это не аргументы, ifа [команда.
Стефан Шазелас
1
Исправление: двойные кавычки не нужны только на левой стороне! [[ $foo == $bar ]]будет выполнять сопоставление с образцом, но [[ $foo == "$bar" ]]не будет.
nyuszika7h
7

Для того, чтобы ответить на вопрос о том, почему это происходит, такое поведение [иначе testкак описано в стандарте POSIX :

В следующем списке $ 1, $ 2, $ 3 и $ 4 представляют аргументы, представленные для проверки:

[...]

1 аргумент:

Выйдите из true (0), если $ 1 не равно нулю; в противном случае выйдите из false.

Вы передаете ему 1 аргумент, 2=1который не является нулевым, и поэтому testзавершается с успехом.

Как указывают другие сообщения (и проверка оболочки ), если вы хотите сравнить на равенство, вам вместо этого нужно будет передать 3 аргумента 2, =и 1.

тот другой парень
источник
3
В некотором смысле это единственный ответ, который ответил на заданный вопрос (вместо того, чтобы дать один или несколько рецептов, которые делают то, что ОП хотел сделать), и оболочка может быть достаточно загадочной, которая знает, почему она делает то, что делает, полезно ,
dmckee --- котенок экс-модератора
1

Я просто хочу порекомендовать портативную, но также более аккуратную альтернативу. Bash не универсален (и если вам не нужен универсальный, зачем вы пишете скрипт оболочки?)

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

(Примечание для педантов: да, я знаю, что в кавычках $actionна case "$action" inстрочке нет необходимости, но я чувствую, что в любом случае лучше поместить их туда, чтобы будущие читатели не должны были это помнить.)

zwol
источник
1
«Bash не является универсальным (и если вам не нужен универсальный, зачем вы пишете сценарий оболочки?)» - это означает, что сценарии bash не имеют прецедентов.
Руслан
@Ruslan Да, это мое взвешенное мнение. Напишите переносимые /bin/shсценарии, если вам это нужно; в противном случае, пишите на языке сценариев, который менее ужасен, чем shell. Базовый интерпретатор Perl на самом деле более вероятно присутствует в устаревших проприетарных средах и урезанных встроенных средах, чем Bash.
zwol