Коли потрібно подвійне цитування?


120

Стара порада використовувалась для подвійного цитування будь-яких виразів, що містять a $VARIABLE, принаймні, якби хотілося, щоб оболонка була інтерпретована як один-єдиний елемент, інакше будь-які пробіли у змісті $VARIABLEскинуть оболонку.

Однак я розумію, що в останніх версіях оболонок подвійне цитування більше не завжди потрібно (принаймні для мети, описаної вище). Наприклад, у bash:

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

З zshіншого боку, ці ж три команди досягають успіху. Отже, виходячи з цього експерименту, здається, що, в bash, можна опустити подвійні лапки всередині [[ ... ]], але не всередині, [ ... ]ані в аргументах командного рядка, тоді як, у zshцих випадках, подвійні лапки можуть бути опущені у всіх цих випадках.

Але виводити загальні правила з таких анекдотичних прикладів, як вище, є суттєвою пропозицією. Було б непогано побачити підсумок, коли необхідно подвійне цитування. Я в першу чергу цікавить zsh, bashі /bin/sh.


10
Ваша спостережувана поведінка в zsh залежить від налаштувань і на них впливає SH_WORD_SPLITпараметр.
Ульріх Дангель


3
Як осторонь - імена змінних all-caps використовуються змінними зі значенням для операційної системи та оболонки; специфікація POSIX явно радить використовувати малі імена для визначених додатком змінних. (Хоча цитується специфікація спеціально орієнтована на змінні середовища, змінні середовища та змінні оболонки діляться простором імен. Спроба створити змінну оболонки з іменем, яке вже використовується змінною оточення, замінює останню). Див. Pubs.opengroup.org/onlinepubs/009695399/basedefs/… , четвертий параграф.
Чарльз Даффі

Відповіді:


128

Спочатку відокремте zsh від решти. Це не справа старих проти сучасних оболонок: zsh поводиться інакше. Дизайнери zsh вирішили зробити його несумісним із традиційними снарядами (Bourne, ksh, bash), але простішими у використанні.

По-друге, набагато простіше використовувати подвійні лапки весь час, ніж згадувати, коли вони потрібні. Вони потрібні більшу частину часу, тому вам потрібно буде вчитися, коли вони не потрібні, а не коли вони потрібні.

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

Що відбувається без лапок

Зауважте, що без подвійних лапок трапляються дві речі.

  1. По-перше, результат розширення (значення змінної для заміни параметра на зразок ${foo}, або вихід команди для заміни команди на зразок $(foo)) розбивається на слова, де б він не містив пробіл.
    Точніше, результат розширення розбивається на кожен символ, який відображається у значенні IFSзмінної (розділовий символ). Якщо послідовність символів роздільника містить пробіл (пробіл, вкладка або новий рядок), пробіл вважається одним символом; провідні, кінцеві або повторні роздільники непробільного простору ведуть до порожніх полів. Наприклад, з IFS=" :", :one::two : three: :four видає порожні поля до one, між oneі two, та (єдине) між threeі four.
  2. Кожне поле, яке є результатом розщеплення, інтерпретується як глобус (шаблон підстановки), якщо він містить один із символів \[*?. Якщо цей шаблон відповідає одному чи більше імен файлів, шаблон замінюється списком відповідних імен файлів.

Розширення без змін, котируемого змінною $foo, відоме як «оператор розділення + глоба», на відміну від "$foo"якого просто приймає значення змінної foo. Те ж саме стосується підстановки команд: "$(foo)"є підміна команд, $(foo)це підміна команд, за якою слідує розділення + glob.

Де можна опустити подвійні лапки

Ось всі випадки, про які я можу придуматись в оболонці в стилі Борна, де ви можете написати змінну або підстановку команд без подвійних лапок, а значення інтерпретується буквально.

  • Праворуч завдання.

    var=$stuff
    a_single_star=*

    Зауважте, що вам потрібні подвійні лапки після export, тому що це звичайний вбудований, а не ключове слово. Це справедливо лише в деяких оболонках, таких як тире, zsh (в емуляції sh), yash або posh; bash і ksh обидва трактують exportспеціально.

    export VAR="$stuff"
  • У caseзаяві.

    case $var in 

    Зауважте, що вам потрібні подвійні лапки у випадку. Розщеплення слів не відбувається в шаблоні випадків, але без котируемой змінної інтерпретується як шаблон, тоді як цитована змінна інтерпретується як буквальний рядок.

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
  • У подвійних дужках. Подвійні дужки є спеціальним синтаксисом оболонки.

    [[ -e $filename ]]

    За винятком того, що вам потрібні подвійні лапки, де очікується візерунок або регулярний вираз: праворуч =або ==або !=або =~.

    a_star='a*'
    if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
    elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
    fi

    Вам потрібні подвійні лапки, як звичайно, в одинарних дужках, [ … ]оскільки вони є звичайним синтаксисом оболонки (це команда, яка, як буває, викликається [). Див. Окремі або подвійні дужки

  • При перенаправлення в неінтерактивні оболонки POSIX (ні bash, ні ksh88).

    echo "hello world" >$filename

    Деякі оболонки, коли вони інтерактивні, розглядають значення змінної як шаблон шаблону. POSIX забороняє таку поведінку в неінтерактивних оболонках, але декілька оболонок, включаючи bash (за винятком режиму POSIX) та ksh88 (у тому числі, коли вони знайдені (нібито) POSIX shдеяких комерційних Unices, таких як Solaris), все одно роблять це там ( bashтакож намагається розколоти і перенаправлення не вдається, якщо тільки розділення + глобулювання не призведе до того, що саме в одному слові), тому краще цитувати цілі переспрямувань у shсценарії, якщо ви хочете перетворити його в bashсценарій якийсь день або запустити його в системі, де shє невідповідний для цього питання, або він може бути отриманий з інтерактивних оболонок.

  • Всередині арифметичного виразу. Насправді, вам потрібно залишити лапки, щоб змінна була проаналізована як арифметичний вираз.

    expr=2*2
    echo "$(($expr))"

    Однак вам потрібні лапки навколо арифметичного розширення, оскільки вони підлягають розщепленню слів у більшості оболонок, як це вимагає POSIX (!?).

  • В асоціативному індексі масиву.

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"

Підстановка змінної без котирувань і команд може бути корисною в деяких рідкісних обставинах:

  • Коли значення змінної або команда виводиться зі списку глобальних шаблонів, і ви хочете розширити ці шаблони до списку відповідних файлів.
  • Коли ви знаєте, що значення не містить символів підстановки, це $IFSне було змінено, і ви хочете розділити його на символи пробілу.
  • Коли ви хочете розділити значення на певний символ: вимкніть глобалізацію set -f, встановіть IFSна роздільник (або залиште його в спокої для розбиття на пробіл), після чого виконайте розширення.

Зш

У zsh ви можете опускати подвійні лапки більшість разів, за кількома винятками.

  • $varніколи не розширюється на кілька слів, проте розширюється до порожнього списку (на відміну від списку, що містить одне порожнє слово), якщо значення varпорожнього рядка. Контраст:

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo

    Аналогічно "${array[@]}"розширюється на всі елементи масиву, тоді $arrayяк розширюється лише на непусті елементи.

  • @Прапор розширення параметра іноді вимагає подвійних лапок всій підстановки: "${(@)foo}".

  • Підстановка команд зазнає розбиття поля, якщо не цитується: echo $(echo 'a'; echo '*')друкується a *(з єдиним пробілом), тоді як echo "$(echo 'a'; echo '*')"друкується немодифікований дворядковий рядок. Використовуйте "$(somecommand)"для отримання результату команди в одному слові, не завершує нові рядки. Використовуйте "${$(somecommand; echo _)%?}"для отримання точного виводу команди, включаючи остаточні нові рядки. Використовуйте "${(@f)$(somecommand)}"для отримання масиву рядків з виводу команди.


Насправді, ви повинні залишити лапки, щоб змінна була проаналізована як арифметичний вираз. Чому я можу змусити ваш приклад працювати з цитатами:echo "$(("$expr"))"
Cyker

Ось що man bashговорить: Вираз трактується так, ніби він знаходиться в подвійних лапках, але подвійний цитат у дужках не трактується спеціально.
Cyker

4
Також для всіх, хто цікавиться, формальні назви split + glob - це розділення слів та розширення імені шляху .
Cyker

3
FYI - на StackOverflow , у цій відповіді хтось тягнув мову "необов'язково, коли очікується необроблена рядок", щоб захистити не цитувати аргумент echo. Можливо, варто спробувати зробити мову ще більш чіткою ("можливо, коли синтаксичний аналог очікує необроблений рядок")
Чарльз Даффі

2
@CharlesDuffy Фу, я не думав про це неправильне прочитання. Я змінив "де" на "коли" і підсилив речення, як ви запропонували.
Жиль
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.