Чому мені потрібно цитувати змінну для, якщо не для відлуння?


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?


2
Якщо ви не цитуєте змінну, яка використовується як аргумент echo, зайві пробіли та нові рядки будуть позбавлені.
jordanm

Відповіді:


36

Вам завжди потрібні лапки навколо змінних у всіх контекстах списку , тобто скрізь змінна може бути розширена до кількох значень, якщо ви не хочете, щоб три побічні ефекти залишали змінну без котирування.

Контексти списку включають аргументи простих команд, таких як, [або 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, $?проведе ряд параметрів він був прийнятий (і це число буде надруковано).


2

Це все про те, як оболонка інтерпретує рядок перед виконанням програми.

Якщо рядок читає echo I am $USER, оболонка розширює її до echo I am blrflі echoне має поняття, чи є походження тексту буквальним чи змінним розширенням. Аналогічно, якщо читається рядок echo I am $UNDEFINED, оболонка розшириться $UNDEFINEDв ніщо, і аргументи ехо будуть I am, і це вже кінець. Оскільки echoпрацює прекрасно без аргументів,echo $UNDEFINED цілком справедливо.

Ваша проблема з ifнасправді не з if, тому що ifпросто запускає будь-яку програму та аргументи за нею і виконує thenчастину, якщо програма закінчується 0(або elseчастину, якщо така є, а програма закінчується не 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вона не бачить, що ви використовуєте змінну. Ерго, [ $UNDEFINED -eq 2 ]перерви, тому що до моменту, коли оболонка робиться з нею, все testбачить аргументи -eq 2 ], що не є правильним тестом. Якби ви зробили це з чимось визначеним, наприклад [ $DEFINED -ne 0 ], що, це працювало б, тому що оболонка розширить його на дійсний тест (наприклад, 0 -ne 0).

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


0

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

Причина, за якою вам не потрібні лапки, щоб розширити змінну, echoполягає в тому, що вона не очікує одного аргументу. Він просто надрукує те, що вам сказали. Тож навіть якщо $testрозгорнеться до 100 слів, відлуння все одно буде друкувати його.

Погляньте на підводні камені Баша


так, але чому нам це не потрібно 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 без нічого наступного. Навіщо.

Цитування також має значення для відлуння у кількох випадках:

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

var="foo        bar"
echo $var
echo "$var"

2
Це не так echo, це оболонка. Ви побачили б таку саму поведінку ls. Спробуйте touch '*'деякий час, якщо ви відчуваєте пригоди. :)
CVn

Це просто формулювання, оскільки немає різниці у випадку "якщо [...]". [не є спеціальною командою оболонки. Це відрізняється від [[(в bash), коли цитування не є необхідним.
Hauke ​​Laging
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.