Когда обертывать кавычки вокруг переменной оболочки?

Может ли кто-нибудь сказать мне, следует ли мне заключать кавычки вокруг переменных в оболочке script?

Например, это правильно:

xdg-open $URL 
[ $? -eq 2 ]

или

xdg-open "$URL"
[ "$?" -eq "2" ]

И если да, то почему?

Ответ 1

Общее правило: укажите его, если оно может быть пустым или содержать пробелы (или любые пробелы) или специальные символы (подстановочные знаки). Не цитирование строк с пробелами часто приводит к тому, что оболочка разбивает один аргумент на многие.

$? не нуждается в котировках, поскольку это числовое значение. Требуется ли $URL зависит от того, что вы там разрешаете, и хотите ли вы аргумент, если он пуст.

Я склонен всегда указывать строки только по привычке, так как это безопаснее.

Ответ 2

Короче говоря, укажите все, где вам не требуется, чтобы оболочка выполняла разделение токенов и расширение подстановочных знаков.

Одиночные кавычки защищают текст между ними дословно. Это правильный инструмент, когда вам нужно убедиться, что оболочка вообще не касается строки. Как правило, это механизм цитирования, когда вам не нужна переменная интерполяция.

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

Двойные кавычки подходят, когда требуется переменная интерполяция. При подходящих приспособлениях это также является хорошим обходным решением, когда вам нужны одинарные кавычки в строке. (Нет простого способа избежать одиночной кавычки между одинарными кавычками, потому что в одинарных кавычках нет механизма выхода - если бы это было, они не цитировали бы полностью дословно.)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

Никакие кавычки не подходят, когда вы специально требуете, чтобы оболочка выполняла разделение токена и/или подстановочное расширение.

Разделение токена,

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

В отличие от этого:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

(цикл выполняется только один раз, по одной, кавычки).

 $ for word in '$words'; do echo "$word"; done
 $words

(цикл работает только один раз, над литеральной строкой с одной кавычкой.)

Расширение подстановочных знаков:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

В отличие от этого:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(Нет файла с именем буквально file*.txt.)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(Нет файла с именем $pattern, либо!)

В более конкретных терминах обычно следует указывать что-либо, содержащее имя файла (поскольку имена файлов могут содержать пробелы и другие метасимволы оболочки). Все, что содержит URL-адрес, должно обычно указываться (поскольку многие URL-адреса содержат метасимволы оболочки, такие как ? и &). Все, что содержит регулярное выражение, обычно следует указывать (то же самое). Все, что содержит значительные пробелы, отличные от одиночных пробелов между не-пробельными символами, должно быть процитировано (поскольку в противном случае оболочка будет обрабатывать пробелы, эффективно, в одиночных пробелах и обрезать любые ведущие или конечные пробелы).

Когда вы знаете, что переменная может содержать только значение, которое не содержит метасимволов оболочки, цитирование является необязательным. Таким образом, некорректный $? в основном прекрасен, потому что эта переменная может содержать только одно число. Тем не менее, "$?" также правилен и рекомендуется для общей согласованности и правильности (хотя это моя личная рекомендация, а не широко признанная политика).

Значения, которые не являются переменными, в основном следуют тем же правилам, хотя вы также можете избежать любых метасимволов вместо их цитирования. Для общего примера URL с & в нем будет анализироваться оболочкой в ​​качестве фоновой команды, если метасимвол не будет экранирован или процитирован:

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(Конечно, это также происходит, если URL-адрес в некотируемой переменной.) Для статической строки одиночные кавычки имеют наибольший смысл, хотя здесь используется любая форма цитирования или экранирования.

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

В последнем примере также предлагается другое полезное понятие, которое я хотел бы назвать "качели". Если вам нужно смешать одиночные и двойные кавычки, вы можете использовать их рядом друг с другом. Например, следующие приведенные строки

'$HOME '
"isn't"
' where `<3'
"' is."

могут быть вставлены вместе назад, образуя одну длинную строку после маркера и удаления цитат.

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

Это не ужасно читаемо, но это обычная техника и, следовательно, хорошо знать.

В качестве стороннего сценария обычно не следует использовать ls для чего-либо. Чтобы развернуть подстановочный знак, просто используйте его.

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(В последнем примере цикл совершенно лишний; printf специально отлично работает с несколькими аргументами. stat тоже. Но цикл по подстановочному совпадению является общей проблемой и часто выполняется неправильно.)

Изменена переменная, содержащая список токенов для циклического перехода или подстановочный знак для расширения, поэтому мы иногда сокращаем "процитировать все, если вы точно не знаете, что делаете".

Ответ 3

Вот трехточечная формула для котировок вообще:

Двойные кавычки

В контексте, где мы хотим подавить расщепление слов и глобусы. Также в контексте, где мы хотим, чтобы литерал обрабатывался как строка, а не регулярное выражение.

Одиночные цитаты

В строковых литералах, где мы хотим подавить интерполяцию и специальное лечение обратных косых черт. Другими словами, ситуации, когда использование двойных кавычек были бы неуместными.

Нет котировок

В контекстах, где мы абсолютно уверены, что нет никаких проблем с расщеплением слов или глобусами, или мы хотим разделить слова и глобусы.


Примеры

Двойные кавычки

  • буквальные строки с пробелами ("StackOverflow rocks!", "Steve Apple")
  • переменные разложения ("$var", "${arr[@]}")
  • ("$(ls)", "'ls'")
  • globs, где путь каталога или часть имени файла включают пробелы ("/my dir/"*)
  • для защиты одинарных кавычек ("single'quote'delimited'string")
  • Расширение параметра Bash ("${filename##*/}")

Одиночные цитаты

  • имена команд и аргументы, в которых есть пробелы
  • литеральные строки, для которых требуется подавление интерполяции ('Really costs $$!', 'just a backslash followed by at: \t')
  • для защиты двойных кавычек ('The "crux"')
  • регулярные выражения, которые должны быть подавлены интерполяцией
  • использовать цитирование оболочки для литералов с участием специальных символов ($'\n\t')
  • используйте оболочку, в которой нам нужно защитить несколько одиночных и двойных кавычек ($'{"table": "users", "where": "first_name"=\'Steve\'}')

Нет котировок

  • вокруг стандартных числовых переменных ($$, $? $# и т.д.)
  • в арифметических контекстах, таких как ((count++)), "${arr[idx]}", "${string:start:length}"
  • внутри [[ ]] выражения, которое не содержит словосочетания и проблемы с глобализацией (это вопрос стиля и мнения могут сильно различаться)
  • где мы хотим разделить слово (for word in $words)
  • где мы хотим globbing (for txtfile in *.txt; do...)
  • где мы хотим ~ интерпретироваться как $HOME (~/"some dir" но не "~/some dir")

Смотрите также:

Ответ 4

Обычно я использую цитату типа "$var" для безопасности, если не уверен, что $var не содержит пробела.

Я использую $var как простой способ соединения строк:

lines="'cat multi-lines-text-file.txt'"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped