Коли обернути лапки навколо змінної оболонки?


184

Може хтось скаже мені, чи слід обробляти цитати навколо змінних у сценарії оболонки?

Наприклад, чи є наступним правильним:

xdg-open $URL 
[ $? -eq 2 ]

або

xdg-open "$URL"
[ "$?" -eq "2" ]

А якщо так, то чому?



Це запитання отримує багато дублікатів, багато з яких не стосуються змінних, тому я знову перейшов до "значення" замість "змінної". Я сподіваюся, що це допоможе більшості людей знайти цю тему.
трійка

1
@codeforester Що відбувається з поверненою редагуванням?
трійка


Відповіді:


131

Загальне правило: цитуйте його, якщо він може бути порожнім або містити пробіли (або будь-які пробіли) або спеціальні символи (макіяж). Не цитування рядків з пробілами часто призводить до того, що оболонка розбиває один аргумент на багато.

$?не потрібні лапки, оскільки це числове значення. Чи $URLпотрібно це, залежить від того, що ви там можете дозволити і чи все ще хочете аргументувати, якщо він порожній.

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


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

4
@ Крістіан: Якщо ви не впевнені, що може бути у змінній, безпечніше це цитувати. Я схильний дотримуватися того самого принципу, що і paxdiablo, і просто звикаю цитувати все (якщо тільки немає конкретної причини цього не робити).
Гордон Девіссон

11
Якщо ви не знаєте значення IFS, цитуйте це незалежно від того. Якщо IFS=0, то echo $?може бути дуже дивно.
Чарльз Даффі

3
Цитата базується на контексті, а не на тому, що ви очікуєте на значення, інакше ваші помилки будуть гіршими. Наприклад, ви впевнені , що жоден із шляхів не мають місця, так що ви думаєте , що ви можете написати cp $source1 $source2 $dest, але якщо з якоїсь - то непередбаченої причини destне чинить приготуйтеся, третій аргумент просто зникає, і він буде мовчки копіювати source1через source2замість даючи вам відповідна помилка для порожнього пункту призначення (як це було б, якби ви цитували кожен аргумент).
Дерек Вейт

3
quote it if...має процес мислення назад - котирування - це не те, що ви додаєте, коли потрібно, це те, що ви видаляєте, коли потрібно. Завжди загортайте рядки та сценарії в єдині лапки, якщо вам не потрібно використовувати подвійні лапки (наприклад, щоб дозволити розширенню змінної) або не потрібно використовувати жодних лапок (наприклад, робити глобалізацію та розширення імен файлів).
Ед Мортон

92

Коротше кажучи, цитуйте все, де вам не потрібна оболонка, щоб виконати розбиття лексеми та розширення підстановки.

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

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

Подвійні лапки підходять, коли потрібна змінна інтерполяція. З відповідними адаптаціями, це також хороший спосіб вирішення, коли вам потрібні поодинокі лапки в рядку. (Немає прямого способу уникнути єдиної цитати між окремими цитатами, оскільки не існує механізму відходу всередині одинарних лапок - якби вони були, вони б не цитували повністю дослівно.)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

Жодні лапки не підходять, коли спеціально потрібно оболонці виконати розбиття лексеми та / або розширення підстановки.

Розбиття токена;

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

На противагу:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

(Цикл працює лише один раз, над єдиним цитуваним рядком.)

 $ for word in '$words'; do echo "$word"; done
 $words

(Цикл виконується лише один раз, через буквальний одноцитуваний рядок.)

Розширення підстановки:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

На противагу:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(Файлу не названо буквально file*.txt.)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(Немає файлу з іменем $pattern!)

Якщо говорити конкретніше, зазвичай слід цитувати все, що містить ім’я файлу (тому що назви файлів можуть містити пробіли та інші метахарактери оболонки). Як правило, цитується все, що містить URL-адресу (оскільки багато URL-адрес містять метахарактеристики оболонки, як ?і &). Все, що містить регекс, зазвичай цитується (ditto ditto). Все, що містить значні пробіли, окрім одинарних пробілів між символами, які не містять пробілів, потрібно цитувати (тому що в іншому випадку оболонка буде об'єднувати пробіли у, фактично, єдині пробіли, та обрізає будь-який провідний чи кінцевий пробіли).

Коли ви знаєте, що змінна може містити лише значення, яке не містить метахарактерів оболонки, цитування не є обов'язковим. Таким чином, без котирування $?в основному добре, оскільки ця змінна може містити лише одне число. Однак "$?"це також правильно і рекомендується для загальної послідовності та коректності (хоча це моя особиста рекомендація, а не широко визнана політика).

Значення, що не є змінними, в основному відповідають одним і тим же правилам, хоча ви можете також уникати будь-яких метахарактерів, а не цитувати їх. У загальному прикладі URL-адреса з &в ній буде проаналізована оболонкою як фонова команда, якщо метахарактер не буде уникнути або процитовано:

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(Звичайно, це також трапляється, якщо URL-адреса є змінною, що не котирується.) Для статичної рядки найбільше сенсу мають одинарні лапки, хоча тут працює будь-яка форма цитування чи скасування.

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

Останній приклад також пропонує іншу корисну концепцію, яку я люблю називати "котируванням пилу". Якщо вам потрібно змішати одинарні та подвійні лапки, ви можете використовувати їх поруч один з одним. Наприклад, наступні цитовані рядки

'$HOME '
"isn't"
' where `<3'
"' is."

можна склеювати спільно назад, утворюючи єдину довгу струну після токенізації та видалення цитат.

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

Це не дуже розбірливо, але це звичайна техніка, і тому добре знати.

Як сторону, сценарії зазвичай не повинні використовуватись lsні для чого. Щоб розгорнути підстановку, просто ... скористайтеся нею.

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(Цикл в останньому прикладі абсолютно зайвий; printfспеціально працює добре з декількома аргументами. statТакож петля над відповідним символом є поширеною проблемою, і часто робиться неправильно.)

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


1
Це варіант (частини) відповіді, яку я опублікував на відповідне запитання . Я вставлю його тут, тому що це досить короткий і чітко визначений, щоб стати канонічним питанням для цієї конкретної проблеми.
трійка

4
Зауважу, що це пункт № 0 і повторювана тема на колекції mywiki.wooledge.org/BashPitfalls з поширеними помилками Баша. Багато, багато окремих пунктів у цьому списку в основному стосуються цього питання.
трійка

27

Ось триточкова формула цитат загалом:

Подвійні цитати

У контекстах, де ми хочемо придушити розщеплення слів і глобалізацію. Також у контекстах, де ми хочемо, щоб літерал трактувався як рядок, а не регулярний вираз.

Одиночні цитати

У рядкових літералах, де ми хочемо придушити інтерполяцію та спеціальну обробку відкосів. Іншими словами, ситуації, коли використання подвійних лапок було б недоцільним.

Без цитат

У контекстах, де ми абсолютно впевнені, що немає жодних проблем з розділенням або глобалізацією слів, або ми хочемо розділити слова і розширити їх .


Приклади

Подвійні цитати

  • буквальні рядки з пробілом ( "StackOverflow rocks!", "Steve's Apple")
  • змінні розширення ( "$var", "${arr[@]}")
  • підстановки команд ( "$(ls)", "`ls`")
  • глобуси, де частина шляху або імені файлу містить пробіли ( "/my dir/"*)
  • для захисту одиничних лапок ( "single'quote'delimited'string")
  • Розширення параметра Bash ( "${filename##*/}")

Одиночні цитати

  • назви команд та аргументи, які містять пробіли
  • буквальні рядки, які потребують придушення інтерполяції ( 'Really costs $$!', 'just a backslash followed by a t: \t')
  • для захисту подвійних лапок ( 'The "crux"')
  • регулярні вирази, які потребують придушення інтерполяції
  • використовувати цитування оболонок для літералів із залученням спеціальних символів ( $'\n\t')
  • використовуйте цитування оболонок там, де нам потрібно захистити кілька одиничних і подвійних лапок ( $'{"table": "users", "where": "first_name"=\'Steve\'}')

Без цитат

  • навколо стандартні числові змінні ( $$, $?, і $#т.д.)
  • в арифметичних контексти подобається ((count++)), "${arr[idx]}","${string:start:length}"
  • всередині [[ ]]висловлювання, що не містить питань розбиття слів і проблем з глобалізацією (це питання стилю і думки можуть сильно відрізнятися)
  • де ми хочемо розділити слова ( for word in $words)
  • де ми хочемо глобус ( for txtfile in *.txt; do ...)
  • де ми хочемо ~тлумачити як $HOME( ~/"some dir"але не "~/some dir")

Дивитися також:


3
Згідно з цими вказівками, можна отримати список файлів у кореневому каталозі, написавши "ls" "/" фразу "всі рядкові контексти", потрібно кваліфікувати більш ретельно.
Вільям Перселл

5
У [[ ]], цитування має значення в правій частині =/ ==і =~: це робить різницю між інтерпретацією рядка як шаблону / регулярного вираження або буквально.
Бенджамін В.

6
Хороший огляд, але коментарі @ BenjaminW. Варто інтегрувати, і рядки, цитовані ANSI C ( $'...'), безумовно, повинні мати свій розділ.
mklement0

3
@ mklement0, дійсно вони рівноцінні. Ці вказівки вказують на те, що завжди слід вводити "ls" "/"замість більш поширених ls /, і я вважаю це головним недоліком в інструкціях.
Вільям Перселл

4
Для без лапок ви можете додати призначення змінної або case:)
PesaThe

4

Як правило, я користуюся цитатами як "$var"безпечні, якщо я не впевнений, що $varце не містить місця.

Я використовую $varяк простий спосіб з'єднання ліній:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped

Остаточний коментар дещо оманливий; нові рядки ефективно замінюються пробілами, а не просто видаляються.
tripleee

-1

Для використання змінних у скрипті оболонки використовуйте цитовані змінні як цитовані, це означає, що змінна може містити пробіли або спеціальний символ, який не впливатиме на виконання сценарію оболонки. В іншому випадку, якщо ви впевнені, що у імені змінної немає пробілів чи спеціального символу, тоді ви можете використовувати їх без "".

Приклад:

echo "$ url name" - (Можна використовувати завжди)

echo "$ url name" - (Не можна використовувати в таких ситуаціях, тому будьте обережні, перш ніж використовувати його)

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