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

184

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

Например, верно ли следующее:

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

или

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

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

Cristian
источник
2
См. Также unix.stackexchange.com/questions/171346/…
tripleee
Этот вопрос содержит много дубликатов, многие из которых не связаны с переменными, поэтому я переименовал «значение» вместо «переменная». Я надеюсь, что это поможет большему количеству людей найти эту тему.
tripleee
1
@codeforester Что случилось с отмененным редактированием?
tripleee

Ответы:

131

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

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

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

paxdiablo
источник
2
Обратите внимание, что «пробелы» действительно означают «любые пробелы».
Уильям Перселл
4
@Cristian: Если вы не уверены, что может быть в переменной, безопаснее заключить ее в кавычки. Я склонен следовать тому же принципу, что и paxdiablo, и просто привыкаю цитировать все (если нет особых причин не делать этого).
Гордон Дэвиссон
11
Если вы не знаете значение IFS, укажите его, несмотря ни на что. Если IFS=0, то echo $?может быть очень удивительно.
Чарльз Даффи
3
Цитируйте, основываясь на контексте, а не на ожидаемых значениях, иначе ваши ошибки будут хуже. Например, вы уверены , что ни один из путей не имеют места, так что вы думаете , что вы можете написать cp $source1 $source2 $dest, но если по какой - то непредвиденной причине destне делает приготовьтесь, третий аргумент просто исчезает, и он будет молча копировать source1через source2вместо давая вам соответствующая ошибка для пустого места назначения (как если бы вы указали каждый аргумент).
Дерек Вейт
3
quote it if...мыслительный процесс задом наперед - кавычки - это не то, что вы добавляете, когда нужно, а то, что вы удаляете, когда это необходимо. Всегда заключайте строки и сценарии в одинарные кавычки, если только вам не нужно использовать двойные кавычки (например, чтобы разрешить расширение переменной) или вам не нужно использовать кавычки (например, чтобы выполнять глобализацию и расширение имени файла).
Эд Мортон
92

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

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

$ 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 Также. Но зацикливание на совпадении с подстановочными знаками является обычной проблемой, и часто выполняется неправильно.)

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

tripleee
источник
1
Это вариант ответа (части), который я разместил на соответствующий вопрос . Я вставляю это здесь, потому что это сжато и достаточно четко определено, чтобы стать каноническим вопросом для этой конкретной проблемы.
tripleee
4
Отмечу, что это пункт № 0 и повторяющаяся тема в коллекции распространенных ошибок Bash mywiki.wooledge.org/BashPitfalls . Многие, многие из отдельных пунктов в этом списке в основном об этой проблеме.
tripleee
27

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

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

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

Одинарные кавычки

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

Без кавычек

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


Примеры

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

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

Одинарные кавычки

  • имена команд и аргументы, в которых есть пробелы
  • строковые литералы, которые должны быть исключены интерполяцией ( 'Really costs $$!','just a backslash followed by a t: \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")

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

codeforester
источник
3
В соответствии с этими рекомендациями можно было бы получить список файлов в корневом каталоге, написав "ls" "/" фразу «все строковые контексты», которую необходимо уточнить.
Уильям Перселл
5
В [[ ]], кавычки имеют значение в правой части =/ ==и =~: это делает разницу между интерпретацией строки как шаблона / регулярного выражения или буквально.
Бенджамин В.
6
Хороший обзор, но комментарии @ BenjaminW. Стоит интегрировать, и ANSI C-цитируемые строки ( $'...') обязательно должны иметь свой собственный раздел.
mklement0
3
@ mklement0, действительно, они эквивалентны. Эти руководящие указания указывают на то, что вы должны всегда печатать "ls" "/"вместо более распространенных ls /, и я считаю это основным недостатком руководящих принципов.
Уильям Перселл
4
Для без кавычек вы можете добавить назначение переменной или case:)
PesaThe
4

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

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

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped
Бах Лиен
источник
Последний комментарий несколько вводит в заблуждение; новые строки эффективно заменяются пробелами, а не просто удаляются.
tripleee
-1

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

Пример:

echo "$ url name" - (Может использоваться всегда)

echo "$ url name" - (не может использоваться в таких ситуациях, поэтому примите меры предосторожности перед его использованием)

Випул Шарма
источник