Цитування в $ (заміна команди) в 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.