Цитування в $ (заміна команди) в Bash


126

У моєму середовищі Bash я використовую змінні, що містять пробіли, і я використовую ці змінні в рамках заміни команд. На жаль, я не можу знайти відповідь на SE.

Який правильний спосіб цитувати мої змінні? І як мені це зробити, якщо вони вкладені?

DIRNAME=$(dirname "$FILE")

чи я цитую за межами заміни?

DIRNAME="$(dirname $FILE)"

або обидва?

DIRNAME="$(dirname "$FILE")"

або я використовую зворотні кліщі?

DIRNAME=`dirname "$FILE"`

Який правильний спосіб це зробити? І як я можу легко перевірити, чи правильно встановлені цитати?


Дивіться також - unix.stackexchange.com/questions/97560/…
— Graeme


1
Це гарне запитання, але враховуючи всі проблеми із вбудованими заготовками, чому б ви ускладнювали життя, використовуючи їх за призначенням?
— Джо

3
@ Джо, із вбудованими пробілами ви маєте на увазі простір у файлах файлів? Особисто я їх часто не використовую, але працюю з каталогами та файлами інших народів, я не впевнений, чи містять пробіли. Крім того, я вважаю, що краще все одразу виправити, тому мені не доведеться хвилюватися в майбутньому.
— CousinCocaine

4
Так. Що ми будемо робити з тими "іншими людьми"? <G>
— Джо

Відповіді:


188

Аби від найгіршого до кращого:

  • DIRNAME="$(dirname $FILE)" не буде робити те, що ви хочете, якщо $FILEмістить пробіли або символи глобусу \[?*.
  • DIRNAME=`dirname "$FILE"`технічно правильно, але зворотні посилання не рекомендуються для розширення команд через додаткову складність при їх вкладенні.
  • DIRNAME=$(dirname "$FILE")правильно, але тільки тому, що це завдання . Якщо ви використовуєте підстановку команд у будь-якому іншому контексті, наприклад, export DIRNAME=$(dirname "$FILE")або du $(dirname "$FILE"), відсутність лапок спричинить проблеми, якщо результат розширення містить пробіли або символи глобулю.
  • DIRNAME="$(dirname "$FILE")"є рекомендованим способом. Ви можете замінити DIRNAME=команду та пробіл, не змінюючи нічого іншого, і dirnameотримаєте правильний рядок.

Щоб ще більше покращитись:

  • DIRNAME="$(dirname -- "$FILE")"працює, якщо $FILEпочинається з тире.
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}"працює, навіть якщо $FILEзакінчується новим рядком, оскільки $()відсікає нові рядки в кінці виводу та dirnameвиводить новий рядок після результату. Шееш dirname, чому ти повинен бути іншим?

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

foo "$(bar "$(baz "$(ban "bla")")")"

Ви не хочете пробувати це із задніми посиланнями.


3
Чітка відповідь на моє запитання. Коли я вкладаю ці змінні, чи можу я просто продовжувати цитувати, як зараз?
— CousinCocaine

3
Чи є посилання / ресурс, що детально описує поведінку лапок в командному підрозділі в лапках?
— AmadeusDrZaius

12
@AmadeusDrZaius " $()Завжди ти завжди створюєш новий контекст цитування", тож це як і поза зовнішніми цитатами. Наскільки я не знаю нічого більше.
— l0b0

4
@ l0b0 Дякую, так, я знайшов ваше пояснення дуже зрозумілим. Мені просто було цікаво, чи є це в посібнику десь також. Я знайшов це (хоча і неофіційно) на шум . Я думаю , якщо ви читаєте про порядок заміщення ретельно , ви могли б отримати цей факт в якості результату.
— AmadeusDrZaius

1
Отже, вкладені лапки є прийнятними, але вони відкидають нас, оскільки більшість схем забарвлення синтаксису не виявляють особливих обставин. Акуратний.
— Люк Девіс

19

Ви завжди можете показувати ефекти змінного котирування за допомогою printf.

Розділення слів зроблено на var1:

$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]

var1 цитується, тож жодне слово розщеплення:

$ printf '[%s]\n' "$var1"
[hello     world]

Розбиття слова var1всередині $(), еквівалентно echo "hello" "world":

$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]

Немає розділення слів var1, немає проблеми з не цитуванням $():

$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]

Розділення слів var1знову:

$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]

Цитуючи обидва, найпростіший спосіб бути впевненим.

$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]

Проблема глобалізації

Не цитування змінної також може призвести до глобального розширення її вмісту:

$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Зауважте, це відбувається лише після розширення змінної. Не потрібно цитувати глобус під час присвоєння:

$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Використовуйте set -fдля відключення такої поведінки:

$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]

І set +fщоб його знову ввімкнути:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]

8
Люди, як правило, забувають, що розщеплення слів - не єдина проблема. Ви можете змінити свій приклад, var1='hello * world'щоб проілюструвати проблему глобалізації.
— Стефан Шазелас

10

Доповнення до прийнятої відповіді:

Хоча я, як правило, погоджуюся з відповіддю @ l0b0 тут, я підозрюю, що розміщення оголених задників у списку "найгіршого до найкращого" є частково результатом припущення, яке $(...)є повсюдно. Я усвідомлюю, що питання задає Bash, але є багато випадків, коли Bash, як виявляється, означає /bin/sh, що, можливо, не завжди є повною оболонкою Bourne Again.

Зокрема, звичайна оболонка Борна не знатиме, що з цим робити $(...), тому сценарії, які стверджують, що сумісні з нею (наприклад, через #!/bin/shлінію shebang), швидше за все, недобре поводяться, якщо ними насправді керує "справжній" /bin/sh- це особливий інтерес, коли, скажімо, виробляють скрипти init або пакують попередні та пост-скрипти, і вони можуть висадити їх у дивному місці під час встановлення базової системи.

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

Сказавши все це, повсюдність оболонок, які реалізують $(...)(Bash, Dash, та ін.), Залишає нам гарне місце для того, щоб дотримуватися гарнішого, легшого в гнізді та останнім часом кращого синтаксису POSIX у більшості випадків, для всі причини @ l0b0 згадує.

Убік: це також періодично з’являється на StackOverflow -


//, Відмінна відповідь. У минулому я також стикався з проблемами сумісності з / bin / sh. Чи є у вас поради, як боротися з його проблемою, використовуючи зворотні сумісні методи?
— Натан Басанес

на моєму досвіді: іноді вам може знадобитися змінити задні позиції на закриті параметри доларів, і не один раз це ніколи не допомагало (потрібно бути конкретним) змінити долар-параден, укладений у бетікет. Я пишу задні посилання в свій код JavaScript, а не в оболонку.
— Стівен Лу
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.