Как правильно заключить в кавычки $ (команда $ arg)?

17

Самое время решить эту головоломку, которая беспокоила меня годами ...

Я встречался с этим время от времени и думал, что это путь:

$(comm "$(arg)")

И думал, что мое мнение было решительно подтверждено опытом. Но я больше не уверен в этом. Shellcheck тоже не может определиться . Это оба:

"$(dirname $0)"/stop.bash
           ^-- SC2086: Double quote to prevent globbing and word splitting.

И:

$(dirname "$0")/stop.bash
^-- SC2046: Quote this to prevent word splitting.

Какая логика позади?

(Это версия Shellcheck 0.4.4, кстати.)

Tomasz
источник
1
Цитировать все замены.
Игнасио Васкес-Абрамс
3
Так же , как это: "$(dirname "$0")"/stop.bash? Кажется, работает ... Что за история?
Томаш
1
Bash достаточно умен, чтобы знать, когда изменился контекст.
Игнасио Васкес-Абрамс
1
Думайте об этом так shell_level_1 $(shell_level_2) shell_level_1: когда вы внутри, $(....)вы вводите «подуровень» оболочки, НО вы можете писать в нем, как если бы вы были на начальном уровне (то есть вы можете писать напрямую "вместо \"и т. д.). Пример: touch "/tmp/a file" ; echo "its size is: $(find "/tmp/a file" -ls | awk '{print $5}) ..." : if you used backticks you'd have to найти \ "/ tmp / файл \" `и print \$5. Без $(...)необходимости: оболочка адаптируется к новому уровню, и вы можете писать напрямую, как будто ваш переводчик теперь тоже на этом уровне.
Оливье Дюлак
«Shellcheck тоже не может определиться». - Два прогона имеют два разных сообщения и указывают в разных местах. Это хочет оба.
Изката

Ответы:

45

Вам нужно использовать "$(somecmd "$file")".

Без кавычек путь с пробелом будет разделен в аргументе somecmd, и он будет нацелен на неправильный файл. Так что вам нужны цитаты изнутри.

Любые пробелы в выходных данных somecmdтакже будут вызывать расщепление, поэтому вам нужны кавычки вне всей подстановки команд.

Кавычки внутри подстановки команд не влияют на кавычки вне ее. Собственное справочное руководство Bash не слишком ясно об этом, но BashGuide прямо упоминает об этом . Текст в POSIX также требует, так как «любой действительный сценарий оболочки» допускается внутри $(...):

В $(command)форме все символы, следующие за открывающей скобкой и совпадающей с закрывающей скобкой, составляют команду. Любой допустимый сценарий оболочки может быть использован для команды, кроме сценария, состоящего исключительно из перенаправлений, который приводит к неопределенным результатам.


Пример:

$ file="./space here/foo"

а. Без кавычек, dirnameобрабатывает как ./spaceи here/foo:

$ printf "<%s>\n" $(dirname $file)
<.>
<here>

б. Кавычки внутри, dirnameпроцессы ./space here/foo, дачи ./space here, которые делятся на две части:

$ printf "<%s>\n" $(dirname "$file")
<./space>
<here>

с. Кавычки снаружи, dirnameобрабатывают оба ./spaceи here/fooвыводят на отдельных строках, но теперь две строки образуют один аргумент :

$ printf "<%s>\n" "$(dirname $file)"
<.
here>

д. Цитаты как внутри, так и снаружи, это дает правильный ответ:

$ printf "<%s>\n" "$(dirname "$file")"
<./space here>

(возможно, было бы проще, если бы dirnameтолько обработал первый аргумент, но это не показало бы разницу между случаями a и c.)

Обратите внимание, что с dirname(и, возможно, с другими) вам также нужно добавить --, чтобы предотвратить использование имени файла в качестве опции на случай, если оно начинается с тире, поэтому используйте "$(dirname -- "$file")".

ilkkachu
источник
16
Примечание: в общем, кавычки не вкладываются в bash; но в этом случае $( )создает новый контекст, поэтому кавычки внутри него не зависят от кавычек вне его. И вы вообще хотите цитаты в обоих контекстах.
Гордон Дэвиссон