Цитирование в $ (подстановка команд) в Bash

126

В моей среде Bash я использую переменные, содержащие пробелы, и я использую эти переменные в подстановке команд. К сожалению, я не могу найти ответ на SE.

Как правильно указать мои переменные? И как мне это сделать, если они вложенные?

DIRNAME=$(dirname "$FILE")

или я цитирую вне подстановки?

DIRNAME="$(dirname $FILE)"

или оба?

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

или я использую обратные галочки?

DIRNAME=`dirname "$FILE"`

Как правильно это сделать? И как я могу легко проверить, правильно ли указаны кавычки?

CousinCocaine
источник
См. Также - unix.stackexchange.com/questions/97560/…
Грэм
1
Это хороший вопрос, но, учитывая все проблемы со встроенными пробелами, с какой стати вы будете усердно трудиться для себя, используя их специально?
Джо
3
@ Джо, со встроенными пробелами ты имеешь в виду пробел в именах файлов? Лично я не использую их так часто, но я работаю с каталогами и файлами других людей, в которых я не уверен, содержат ли они пробелы. Кроме того, я думаю, что лучше сразу сделать все правильно, чтобы мне не пришлось беспокоиться в будущем.
Кузен Кокаин
4
Да. Что мы будем делать с этими "другими людьми"? <G>
Джо

Ответы:

188

В порядке от худшего к лучшему:

  • DIRNAME="$(dirname $FILE)" не будет делать то, что вы хотите, если $FILEсодержит пробелы или символы Globbing \[?*.
  • 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")")")"

Вы не хотите пробовать это с помощью обратных кавычек.

l0b0
источник
3
Четкий ответ на мой вопрос. Когда я вкладываю эти переменные, могу ли я просто продолжать цитировать, как сейчас?
Кузен Кокаин
3
Есть ли ссылка / ресурс, подробно описывающий поведение кавычек в подстановке команд внутри кавычек?
AmadeusDrZaius
12
@AmadeusDrZaius "С $()вами всегда создайте новый контекст цитирования", так что это как вне внешних кавычек. Насколько мне известно, больше ничего нет.
10
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]
Graeme
источник
8
Люди склонны забывать, что расщепление слов - не единственная проблема, вы можете изменить свой пример, var1='hello * world'чтобы проиллюстрировать проблему глобализации.
Стефан Шазелас
10

Дополнение к принятому ответу:

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

В частности, простая оболочка Bourne не будет знать, что делать с ней $(...), поэтому сценарии, которые утверждают, что совместимы с ней (например, через #!/bin/shстроку shebang), вероятно, будут вести себя неправильно, если они на самом деле управляются «реальными» /bin/sh- это особый интерес, когда, скажем, создание сценариев инициализации или упаковка предварительных и постскриптовых сценариев и может привести к их неожиданному расположению во время установки базовой системы.

Если что-то из этого звучит как то, что вы планируете делать с этой переменной, вложение, вероятно, не так важно, как на самом деле предсказуемо запустить скрипт. Когда дело обстоит достаточно просто, и переносимость вызывает беспокойство, даже если я ожидаю, что скрипт обычно запускается в системах, где /bin/shустановлен Bash, по этой причине я часто склоняюсь к использованию обратных галочек с несколькими назначениями вместо вложенности.

Сказав все это, повсеместное распространение оболочек, которые реализуют $(...)(Bash, Dash и др.), Оставляет нам хорошее место для того, чтобы придерживаться более красивого, удобного для размещения и более позднего предпочтительного синтаксиса POSIX в большинстве случаев, для все причины, о которых упоминает @ l0b0.

В сторону: это иногда появлялось и в StackOverflow -

rsandwick3
источник
// Отличный ответ. Я также сталкивался с проблемами обратной совместимости с / bin / sh в прошлом. Есть ли у вас какие-либо советы о том, как справиться с его проблемой, используя обратно совместимые методы?
Натан Басанезе
по моему опыту: иногда вам может понадобиться поменять кавычки на долларовые скобки в скобках, и ни разу это не помогло (было необходимо, если быть точным) менять долларовые скобки в скобках. Я пишу только обратные пометки в моем коде JavaScript, а не в коде оболочки.
Стивен Лу