Який правильний спосіб процитувати $ (команда $ 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, btw.)


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. З $(...)немає необхідності: оболонка адаптується до нового рівня , і ви можете написати безпосередньо , як якщо ваш перекладач зараз на цьому рівні теж.
Олів’є Дулак

"Шеллчек теж не може вирішити свою думку". - Два запуски мають два різних повідомлення та вказують на різні місця. Вона хоче обох.
Ізката

Відповіді:


45

Вам потрібно користуватися "$(somecmd "$file")".

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

Будь-які пробіли у висновку somecmdтакож спричинить розщеплення, тому вам потрібні лапки на зовнішній стороні всієї заміни команди.

Цитати всередині заміни команди не впливають на лапки поза нею. Власне посібник Баша щодо цього не надто зрозумілий, але 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>

c. Котирування назовні, dirnameобробляє і, ./spaceі here/fooвиводить в окремі рядки, але тепер два рядки утворюють єдиний аргумент :

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

г. Цитати як всередині, так і зовні, це дає правильну відповідь:

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

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

Зауважте, що dirname(і, можливо, інші) вам також потрібно додати --, щоб запобігти тому, щоб ім’я файлу не було прийнято як варіант, якщо воно трапиться з тире, тому використовуйте "$(dirname -- "$file")".


16
Примітка. Загалом, котирування не гніздяться в башті; але в цьому випадку $( )створюється новий контекст, тому цитати всередині нього не залежать від цитат поза ним. І ти, як правило, хочеш цитати в обох контекстах.
Гордон Девіссон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.