Зачем мне нужно указывать переменную для if, но не для echo?

26

Я читал, что вам нужны двойные кавычки для расширения переменных, например

if [ -n "$test" ]; then echo '$test ok'; else echo '$test null'; fi

будет работать как положено, пока

if [ -n $test ]; then echo '$test ok'; else echo '$test null'; fi

всегда будет говорить, $test okдаже если $testэто ноль.

но тогда почему нам не нужны кавычки echo $test?

CharlesB
источник
2
Если вы не заключите в кавычки переменную, которая будет использоваться в качестве аргумента echo, лишние пробелы и символы новой строки будут удалены.
Иордания

Ответы:

36

Вам всегда нужны кавычки вокруг переменных во всех контекстах списка , то есть везде, где переменная может быть расширена до нескольких значений, если вы не хотите, чтобы 3 побочных эффекта оставляли переменную без кавычек.

списочные контексты включают в себя аргументы для простых команд, таких как, [или echo, for i in <here>присваивания массивам ... Существуют и другие контексты, в которых переменные также должны заключаться в кавычки. Лучше всего всегда заключать в кавычки переменные, если у вас нет веских причин не делать этого.

Думайте об отсутствии кавычек (в контексте списка) как оператора split + glob .

Как будто echo $testбыл echo glob(split("$test")).

Поведение оболочки сбивает с толку большинство людей, потому что в большинстве других языков вы помещаете кавычки вокруг фиксированных строк, например puts("foo"), а не вокруг переменных (например puts(var)), в то время как в оболочке все наоборот: все является строкой в ​​оболочке, так что кавычки вокруг всего было бы обременительно, вам echo test, вам не нужно "echo" "test". В оболочке кавычки используются для чего-то другого: предотвращают какое-то особое значение некоторых символов и / или влияют на поведение некоторых расширений.

В [ -n $test ]или echo $testоболочка разделится $test(по умолчанию на пустые места), а затем выполнит генерацию имени файла (развернет все *шаблоны '?' ... до списка подходящих файлов), а затем передаст этот список аргументов командам [или echo,

Опять же, подумайте об этом как "[" "-n" glob(split("$test")) "]". Если $testоно пустое или содержит только пробелы (spc, tab, nl), то оператор split + glob вернет пустой список, поэтому [ -n $test ]будет "[" "-n" "]", который проверяет, является ли «-n» пустой строкой или нет. Но представьте, что случилось бы, если бы $testбыло "*" или "= foo" ...

В [ -n "$test" ], [передаются четыре аргумента "[", "-n", ""и "]"(без кавычек), который является то , что мы хотим.

Независимо от того, имеет это значение echoили [нет, просто echoвыводит одно и то же, независимо от того, был ли передан пустой аргумент или нет аргумента вообще.

Смотрите также этот ответ на аналогичный вопрос для более подробной информации о [команде и [[...]]конструкции.

Стефан Шазелас
источник
7

Ответ @ h3rrmiller хорош для объяснения, почему вам нужны кавычки для if(или, скорее, [/ test), но я бы на самом деле предположил, что ваш вопрос неверен.

Попробуйте следующие команды, и вы поймете, что я имею в виду.

export testvar="123    456"
echo $testvar
echo "$testvar"

Без кавычек подстановка переменных приводит к расширению второй команды до:

echo 123    456

и несколько пробелов свернуты в один:

echo 123 456

С кавычками пробелы сохраняются.

Это происходит потому , что , когда вы цитируете параметр (независимо от того , что передаваемый параметр echo, testили какой -либо другой команды), то значение этого параметра передается в качестве одного значения к команде. Если вы не заключите его в кавычки, оболочка выполняет обычную магию поиска пробелов, чтобы определить, где начинается и заканчивается каждый параметр.

Это также может быть проиллюстрировано следующей (очень очень простой) C-программой. Попробуйте выполнить следующую команду в командной строке (вы можете сделать это в пустой директории, чтобы не рисковать перезаписью чего-либо).

cat <<EOF >paramtest.c
#include <stdio.h>
int main(int argc, char **argv) {
  int nparams = argc-1; /* because 1 parameter means only the executable's name */
  printf("%d parameters received\n", nparams);
  return nparams;
}
EOF
cc -o paramtest paramtest.c

а потом...

./paramtest 123 456
./paramtest "123 456"
./paramtest 123   456
./paramtest "123   456"

После бега paramtest, $?проведет ряд параметров он был принят (и это число будет напечатано).

CVn
источник
2

Это все о том, как оболочка интерпретирует строку перед выполнением программы.

Если строка читается echo I am $USER, оболочка раскрывает ее echo I am blrflи echoне имеет понятия, является ли источник текста литералом или переменным расширением. Точно так же, если строка читает echo I am $UNDEFINED, оболочка не расширится $UNDEFINEDдо нуля, и аргументы echo будут I am, и на этом все. Так как echoработает просто отлично без аргументов, echo $UNDEFINEDполностью действителен.

Ваша проблема с ifне действительно if, потому что ifпросто запускает любую программу и аргументы, которые следуют за ней, и выполняет thenчасть, если программа выходит 0(или elseчасть, если она есть, и программа выходит из non- 0):

if /bin/true ; then echo True dat. ; fi
if fgrep -q blrfl /etc/passwd ; then echo Blrfl has an account. ; fi

Когда вы используете if [ ... ]для сравнения, вы не используете примитивы, встроенные в оболочку. Вы на самом деле инструктируете оболочку для запуска программы, [которая называется очень небольшой надмножество, для test(1)которого требуется последний аргумент ]. Обе программы завершаются, 0если условие теста выполнилось, а 1если нет.

Причина, по которой некоторые тесты ломаются, когда переменная не определена, заключается в том, что testона не видит, что вы используете переменную. Ergo, [ $UNDEFINED -eq 2 ]ломается, потому что к тому времени, когда оболочка работает с ней, все testвидит аргументы -eq 2 ], что не является допустимым тестом. Если вы сделали это с чем-то определенным, например [ $DEFINED -ne 0 ], это сработало бы, потому что оболочка превратила бы его в действительный тест (например, 0 -ne 0).

Существует семантическая разница между ними foo $UNDEFINED bar, которая расширяется до двух аргументов ( fooи bar), поскольку $UNDEFINEDсоответствует своему названию. Сравните это с foo "$UNDEFINED" bar, который расширяется до трех аргументов ( fooпустая строка и `bar). Кавычки заставляют оболочку интерпретировать их как аргумент, есть ли что-то между ними или нет.

Blrfl
источник
0

Без кавычек $testможно расширить до более чем одного слова, поэтому его нужно заключать в кавычки, чтобы не нарушать синтаксис, поскольку каждый переключатель внутри [команды ожидает один аргумент, который делает кавычки (превращает все, что $testрасширяется в один аргумент)

Причина, по которой вам не нужны кавычки для расширения переменной, echoзаключается в том, что она не ожидает одного аргумента. Он просто напечатает то, что вы говорите. Так что, даже если $testрасшириться до 100 слов, эхо все равно напечатает его

Взгляните на Bash Pitfalls

h3rrmiller
источник
да, но зачем нам это нужно echo?
CharlesB
@CharlesB Вам нужны кавычки для echo. Что заставляет вас думать иначе?
Жиль "ТАК - перестань быть злым"
Мне они не нужны, я могу, echo $testи это работает (выводит значение $ test)
CharlesB
1
@CharlesB Выводит значение $ test только в том случае, если оно нигде не содержит несколько пробелов. Попробуйте программу в моем ответе для иллюстрации причины.
CVn
0

Пустые параметры удаляются, если не указаны:

start cmd:> strace -e trace=execve echo foo $bar baz
execve("/usr/bin/echo", ["echo", "foo", "baz"], [/* 100 vars */]) = 0

start cmd:> strace -e trace=execve echo foo "$bar" baz
execve("/usr/bin/echo", ["echo", "foo", "", "baz"], [/* 100 vars */]) = 0

Вызываемая команда не видит, что в командной строке оболочки был пустой параметр. Похоже, что [определено так, что оно возвращает 0 для -n, но ничего не следует. Whyever.

Цитирование также имеет значение для эха, в некоторых случаях:

var='*'
echo $var
echo "$var"

var="foo        bar"
echo $var
echo "$var"
Хауке Лагинг
источник
2
Это не так echo, это оболочка. Вы бы увидели такое же поведение с ls. Попробуйте touch '*'некоторое время, если вы чувствуете себя авантюрным. :)
CVn
Это просто формулировка, поскольку нет разницы в случае «если [...]». [не является специальной командой оболочки. Это отличается от [[(в bash), где цитирование не обязательно.
Хауке Лэнг