Последствия для безопасности: забыть заключить переменную в оболочку bash / POSIX


206

Если вы следили за unix.stackexchange.com какое-то время, вы должны надеяться, что уже знаете, что оставление переменной без кавычек в контексте списка (как в echo $var) в оболочках Bourne / POSIX (исключение zsh) имеет очень особое значение и не должно быть сделано, если у вас нет очень веских причин.

Это обсуждается в ряде Q & A здесь (примеры: ? Почему мой заслонку сценарий оболочки на пробел или другие специальные символы , Когда двойные кавычки необходимо? , Расширение переменной оболочки и эффект Глоб и раскола на нем , Цитируется расширение строки без кавычек)

Это имело место с момента первоначального выпуска оболочки Bourne в конце 70-х годов и не было изменено оболочкой Korn (одно из самых больших сожалений Дэвида Корна (вопрос № 7) ) или bashкоторая в основном копировала оболочку Korn, и это как это было указано в POSIX / Unix.

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

По моему опыту, в основном есть 3 типа людей, которые опускают в кавычки свои переменные:

  • начинающих. Это может быть оправдано тем, что это совершенно не интуитивный синтаксис. И наша роль на этом сайте - обучать их.

  • забывчивые люди.

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

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

Что хуже всего, чем может случиться, если вы забудете процитировать свои переменные. Действительно ли это , что плохо?

О какой уязвимости мы говорим здесь?

В каких случаях это может быть проблемой?


8
Думаю, вам понравится BashPitfalls .
pawel7318


5
Я хотел бы предложить добавить четвертую группу: людей, которых слишком часто били по голове чрезмерным цитированием, возможно, члены третьей группы выражали свое разочарование другим (жертва становится хулиганом). Грустная вещь, конечно, в том, что люди из четвертой группы могут в конечном итоге не цитировать вещи, когда это имеет наибольшее значение.
Ack

Ответы:


201

преамбула

Во-первых, я бы сказал, что это неправильный способ решения проблемы. Это все равно, что сказать: « Вы не должны убивать людей, потому что иначе вы попадете в тюрьму ».

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

Небольшое резюме для тех, кто только что прыгнул на поезд.

В большинстве оболочек расширение переменной без кавычек (хотя это (и остальная часть этого ответа) также относится к подстановке команд ( `...`или $(...)) и арифметическому расширению ( $((...))или $[...])) имеет совершенно особое значение. Лучший способ описать это так: это все равно что вызывать некий неявный оператор split + globb .

cmd $var

на другом языке было бы написано что-то вроде:

cmd(glob(split($var)))

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

Например, если он $varсодержит *.txt,/var/*.xmlи $IFS содержит ,, cmdбудет вызван с несколькими аргументами, первый из которых будет, cmdа следующие - txt файлы в текущем каталоге и xmlфайлы в /var.

Если вы хотите вызвать cmdтолько с двумя буквальными аргументами cmd и *.txt,/var/*.xml, вы бы написали:

cmd "$var"

который будет на вашем другом более знакомом языке:

cmd($var)

Что мы подразумеваем под уязвимостью в оболочке ?

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

Что ж, несмотря на тот факт, что кто-то скажет вам, что сценарии оболочки никогда не должны использоваться для веб-CGI, или что, к счастью, большинство систем в настоящее время не допускают сценарии оболочки setuid / setgid, это единственная вещь, которая вызывает шеллшок (удаленно эксплуатируемая ошибка bash, которая сделала заголовки в сентябре 2014 г.) показали, что скорлупа по - прежнему широко используется там , где они , вероятно , не следует: в CGIs, в DHCP - клиент скрипты ловушек, в sudoers команд, вызываемых с помощью (если не как ) Setuid команды ...

Иногда по незнанию. Например, system('cmd $PATH_INFO') в php/ perl/ pythonCGI-скрипте вызывается оболочка для интерпретации этой командной строки (не говоря уже о том, что он cmdсам может быть сценарием оболочки и его автор, возможно, никогда не ожидал, что он будет вызван из CGI).

У вас есть уязвимость, когда есть путь для повышения привилегий, то есть когда кто-то (назовем его атакующим ) может сделать то, для чего он не предназначен.

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

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

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

Что касается переменных, то в случае сценария CGI совершенно очевидно, что данные представляют собой параметры CGI GET / POST и такие вещи, как cookie, path, host ... parameters.

Для сценария setuid (запускается как один пользователь при вызове другим) это аргументы или переменные окружения.

Другой очень распространенный вектор - это имена файлов. Если вы получаете список файлов из каталога, возможно, что файлы были установлены злоумышленником .

В связи с этим даже при появлении интерактивной оболочки вы можете быть уязвимы (например, при обработке файлов в /tmpили ~/tmp ).

Даже a ~/.bashrcможет быть уязвимым (например, bashбудет интерпретировать его при вызове sshдля запуска ForcedCommand аналогичного gitразвертывания сервера с некоторыми переменными под контролем клиента).

Теперь скрипт не может быть вызван напрямую для обработки ненадежных данных, но он может быть вызван другой командой, которая это делает. Или ваш неправильный код может быть скопирован в скрипты, которые делают (вы 3 года подряд или один из ваших коллег). Одним из мест, где это особенно важно, являются ответы на сайтах вопросов и ответов, так как вы никогда не узнаете, где могут оказаться копии вашего кода.

Вниз к делу; насколько плохо?

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

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

Существует бесконечное количество способов, которыми переменная без кавычек может превратиться в уязвимость. Я просто приведу несколько общих тенденций здесь.

Раскрытие информации

Большинство людей сталкиваются с ошибками, связанными с переменными без кавычек из-за разделенной части (например, в настоящее время файлы имеют пробелы в своих именах, а пробел находится в значении по умолчанию IFS). Многие люди будут упускать из виду глобус часть. Часть шара по меньшей мере так же опасна, как и разделенная часть.

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

В:

echo You entered: $unsanitised_external_input

если $unsanitised_external_inputсодержит /*, это означает, что злоумышленник может видеть содержимое /. Ничего страшного. Тем не менее, становится интереснее то, /home/*что дает вам список имен пользователей на машине /tmp/*, /home/*/.forwardдля подсказок о других опасных практиках, /etc/rc*/*для включенных служб ... Не нужно называть их по отдельности. Значение /* /*/* /*/*/*...просто перечислит всю файловую систему.

Уязвимости отказа в обслуживании.

Взяв предыдущий случай слишком далеко, мы получили DoS.

Фактически, любая переменная без кавычек в контексте списка с неанизированным вводом является, по крайней мере, уязвимостью DoS.

Даже опытные сценаристы оболочки обычно забывают цитировать такие вещи, как:

#! /bin/sh -
: ${QUERYSTRING=$1}

:это команда no-op. Что возможно могло пойти не так?

Это означало , чтобы назначить $1для $QUERYSTRINGесли $QUERYSTRING было отключено. Это быстрый способ сделать CGI-скрипт доступным из командной строки.

Это $QUERYSTRINGвсе еще расширяется, и поскольку он не заключен в кавычки, вызывается оператор split + glob .

Теперь есть некоторые глобусы, которые особенно дороги в расширении. /*/*/*/*Один достаточно плохо , как это означает , что листинг каталогов до 4 -х уровней вниз. Помимо работы с диском и процессором, это означает хранение десятков тысяч путей к файлам (здесь 40 КБ на минимальной виртуальной машине сервера, из которых 10 КБ каталогов).

Теперь /*/*/*/*/../../../../*/*/*/*означает 40k x 10k и этого /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*достаточно, чтобы поставить на колени даже самую мощную машину.

Попробуйте сами (хотя будьте готовы к поломке или зависанию вашей машины):

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

Конечно, если код:

echo $QUERYSTRING > /some/file

Затем вы можете заполнить диск.

Просто выполните поиск в Google по shell cgi или bash cgi или ksh cgi , и вы найдете несколько страниц, которые покажут вам, как писать CGI в оболочках. Обратите внимание, что половина тех, которые обрабатывают параметры, уязвимы.

Даже собственный Дэвид Корн уязвим (посмотрите на обработку куки).

до произвольных уязвимостей при выполнении кода

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

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

awk -v foo=$external_input '$2 == foo'

Здесь предполагалось назначить содержимое $external_inputпеременной оболочки foo awkпеременной.

В настоящее время:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

Второе слово, полученное в результате разбиения, $external_input не присваивается, fooа рассматривается как awkкод (здесь, который выполняет произвольную команду:) uname.

Это особенно проблема для команд , которые могут выполняться другие команды ( awk, env, sed(GNU один) perl, find...) , особенно с вариантами GNU (которые принимают параметры после аргументов). Иногда вы не подозреваете, что команды могут выполнять другие, такие как ksh, bashили zsh, [или printf...

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

Если мы создадим каталог с именем x -o yes, тогда тест станет положительным, потому что это абсолютно другое условное выражение, которое мы оцениваем.

Хуже того, если мы создадим файл с именем x -a a[0$(uname>&2)] -gt 1, по крайней мере, со всеми реализациями ksh (который включает в себя sh большинство коммерческих Unices и некоторые BSD), который выполняется, uname потому что эти оболочки выполняют арифметическую оценку операторов числового сравнения [команды.

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

То же самое с bashименем файла, как x -a -v a[0$(uname>&2)].

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

Все виды вещей могут быть сделаны с именами файлов.

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

И вы в итоге делаете ..доступным для записи (рекурсивно с GNU chmod).

Сценарии, выполняющие автоматическую обработку файлов в общедоступных областях, например /tmp, должны быть написаны очень осторожно.

Что о [ $# -gt 1 ]

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

Это как сказать. Эй, похоже, на него $#не распространяется оператор split + glob, давайте попросим оболочку split + glob . Или , давайте напишем неправильный код только потому, что ошибка вряд ли будет устранена .

Насколько это маловероятно? ОК, $#(или $!, $?или какая - либо арифметическая замещение) может содержать только цифры (или -для некоторых) , так что Глоб часть выходит. Чтобы разделенная часть могла что-то делать, нам нужно $IFSтолько содержать цифры (или -).

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

Теперь, если вы напишите такую ​​функцию:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

Это означает, что поведение вашей функции зависит от контекста, в котором она вызывается. Или, другими словами, $IFS становится одним из входов к нему. Строго говоря, когда вы пишете API-документацию для своей функции, она должна выглядеть примерно так:

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

И код, вызывающий вашу функцию, должен убедиться, что $IFSона не содержит цифр. Все из-за того, что вам не хотелось печатать эти две двойные кавычки.

Теперь, чтобы эта [ $# -eq 2 ]ошибка стала уязвимостью, вам нужно как-то, чтобы значение $IFSоказалось под контролем злоумышленника . Вполне возможно, что это не произойдет, если злоумышленнику не удастся использовать другую ошибку.

Это не неслыханно, хотя. Распространенный случай - когда люди забывают очистить данные, прежде чем использовать их в арифметическом выражении. Мы уже видели выше, что он может разрешить выполнение произвольного кода в некоторых оболочках, но во всех из них он позволяет злоумышленнику дать любой переменной целочисленное значение.

Например:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

А со $1значением «а» (IFS=-1234567890)эта арифметическая оценка имеет побочный эффект настроек IFS, и следующая [ команда не выполняется, что означает, что проверка на слишком много аргументов обойдена.

Как насчет того, когда оператор split + glob не вызывается?

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

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

не испытывают ли $aи $bте же ( за исключением zsh) , но если $aсоответствует шаблону в $b. И вам нужно заключать в кавычки, $bесли вы хотите сравнивать как строки (то же самое в "${a#$b}"или "${a%$b}"или "${a##*$b*}"где $bследует заключать в кавычки, если это не следует воспринимать как образец).

Это означает, что он [[ $a = $b ]]может возвращать true в случаях, когда $aон отличается от $b(например, когда $aесть anythingи $bесть *), или может возвращать false, когда они идентичны (например, когда оба $aи $bявляются [a]).

Это может сделать для уязвимости безопасности? Да, как любая ошибка. Здесь злоумышленник может изменить поток логического кода вашего скрипта и / или нарушить предположения, которые делает ваш скрипт. Например, с кодом вроде:

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

Атакующий может обойти проверку, пройдя мимо '[a]' '[a]'.

Теперь, если ни это сопоставление с образцом, ни оператор split + glob не применимы, какова опасность оставить переменную без кавычек?

Я должен признать, что я пишу:

a=$b
case $a in...

Там цитирование не вредит, но не является строго необходимым.

Тем не менее, одним из побочных эффектов пропуска кавычек в этих случаях (например, в ответах на вопросы и ответы) является то, что он может отправлять неправильное сообщение новичкам: может быть, все в порядке, не заключая в кавычки переменные .

Например, они могут начать думать, что если a=$bвсе в порядке, то export a=$bэто будет так же хорошо (что не во многих оболочках, а в аргументах exportкоманды и в контексте списка) или env a=$b.

Как насчет zsh?

zshдействительно исправил большинство из этих дизайнерских неудобств. В zsh(по крайней мере, когда не в режиме эмуляции sh / ksh), если вы хотите расщепление , или подстановку , или сопоставление с образцом , вы должны явно запросить это: $=varрасщепление и $~varвыделение или для содержимого переменной, которая будет рассматриваться как шаблон.

Тем не менее, разделение (но не глобализация) все еще выполняется неявно при замене команд без кавычек (как в echo $(cmd)).

Кроме того, иногда нежелательным побочным эффектом отсутствия кавычек является удаление тары . zshПоведение похоже на то , что вы можете достичь в других оболочках, отключив подстановку вообще (с set -f) и расщепление (с IFS=''). Еще в:

cmd $var

Не будет split + glob , но если $varон пуст, вместо получения одного пустого аргумента, cmdон вообще не получит аргумента.

Это может вызвать ошибки (как очевидное [ -n $var ]). Это может нарушить ожидания и предположения сценария и стать причиной уязвимостей, но я не могу сейчас привести не слишком надуманный пример).

А когда вы делаете нужен раскол + Glob оператор?

Да, обычно вы хотите оставить свою переменную без кавычек. Но затем вам нужно убедиться, что вы правильно настроили операторы split и glob, прежде чем использовать его. Если вам нужна только разделенная часть, а не глобальная часть (что имеет место в большинстве случаев), вам нужно отключить глобализацию ( set -o noglob/ set -f) и исправить $IFS. В противном случае вы также создадите уязвимости (как в приведенном выше примере CGI Дэвида Корна).

Заключение

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

Это одна из причин, почему это считается плохой практикой .

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

(и в случае, если это еще не очевидно, пожалуйста, избегайте написания кода, чувствительного к безопасности, в оболочках).

И, пожалуйста, укажите ваши переменные в ваших ответах на этом сайте!


And В ksh93и pdkshи производных расширение фигурных скобок также выполняется, если не отключено ksh93глобирование (в случае версий до ksh93u +, даже если braceexpandопция отключена).


Обратите внимание, что при [[этом нужно указывать только RHS сравнений:if [[ $1 = "$2" ]]; then
mirabilos

2
@mirabilos, да, но LHS потребности не не котироваться, так что нет никаких убедительных причин , чтобы не процитировать его там (если мы принять сознательное решение цитировать по умолчанию , как это кажется наиболее разумным , что нужно сделать ). Также обратите внимание, что [[ $* = "$var" ]]это не то же самое, что [[ "$*" = "$var" ]]если первый символ $IFSне является пробелом bash(а также mkshесли $IFSпусто, хотя в этом случае я не уверен, что $*равно, должен ли я вызвать ошибку?)).
Стефан Шазелас

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

2
@ Barar, если вы имели в виду foo='bar; rm *', нет, это не будет, однако он будет перечислять содержимое текущего каталога, который может рассматриваться как раскрытие информации. print $fooв ksh93(где printзамена для echoэтого устраняет некоторые из его недостатков) имеет уязвимость внедрения кода, хотя (например, с foo='-f%.0d z[0$(uname>&2)]') (вам на самом деле нужно print -r -- "$foo". echo "$foo"все еще неправильно и не исправимо (хотя обычно менее опасно)).
Стефан Шазелас

3
Я не эксперт по Bash, но я кодирую его более десяти лет. Я часто использовал кавычки, но в основном для обработки встроенных пробелов. Теперь я буду использовать их намного больше! Было бы неплохо, если бы кто-то расширил этот ответ, чтобы было легче усвоить все тонкости. Я получил много, но я тоже много пропустил. Это уже длинный пост, но я знаю, что здесь есть чему поучиться. Спасибо!
Джо

34

[Вдохновленный этот ответ по КАСУ .]

Но что, если …?

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

А что, если одним из возможных значений является пустая строка, а я в зависимости от «удаления тары»? Т.е., если переменная содержит пустую строку, я не хочу получать пустую строку в моей команде; Я ничего не хочу получить. Например,

если какое-то условие
тогда
    IGNORECASE = "- я"
еще
    IGNORECASE = ""
фи
                                        # Обратите внимание, что кавычки в вышеприведенных командах не являются строго необходимыми. 
grep $ ignorecase   other_ grep _args

Не могу сказать ; это не удастся, если это пустая строка.grep "$ignorecase" other_grep_args$ignorecase

Отклик:

Как обсуждалось в другом ответе, это все равно не удастся, если IFSсодержит a -или an i. Если вы IFSубедились, что в вашей переменной нет ни одного символа (и вы уверены, что ваша переменная не содержит символов-глобусов), то это, вероятно, безопасно.

Но есть способ, который более безопасен (хотя он несколько уродлив и совершенно не интуитивен): использовать ${ignorecase:+"$ignorecase"}. Из спецификации языка команд оболочки POSIX , в разделе  2.6.2 Расширение параметров ,

${parameter:+[word]}

    Используйте альтернативное значение.   Если parameterне установлено или равно нулю, то следует заменить ноль; в противном случае, расширение word (или пустая строка, если wordопущено) должно быть заменено.

Хитрость здесь, такая как она есть, в том, что мы используем ignorecaseкак parameter и "$ignorecase"как word. Так ${ignorecase:+"$ignorecase"}значит

Если $ignorecaseне установлено или равно нулю (т. Е. Пусто), ноль (т. Е. Ничего не заключенное в кавычки ) должно быть заменено; в противном случае расширение "$ignorecase"должно быть заменено.

Это приведет нас туда, куда мы хотим пойти: если для переменной задана пустая строка, она будет «удалена» (это целое, запутанное выражение будет ничего не вычислять - даже пустую строку), и если переменная не имеет - пустое значение, мы получаем это значение в кавычках.


Но что, если …?

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

Например,

если какое-то условие
тогда
    критерий = "- тип f"
еще
    Критерии = «»
фи
если какое-то другое условие
тогда
    критерии = "$ критерии -mtime +42"
фи
найти "$ start_directory" $ критерий   other_ find _args

Отклик:

Вы можете подумать, что это случай использования eval.  Нет!   Не поддавайтесь искушению даже подумать об использовании evalздесь.

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

Но если вы используете bash (или ksh, zsh или yash), есть более безопасный способ: использовать массив:

если какое-то условие
тогда
    критерии = (- тип f) # Вы можете сказать `crit = (" - тип "" f ")`, но это действительно не нужно.
еще
    критерии = () # Не используйте кавычки в этой команде!
фи
если какое-то другое условие
тогда
    критерий + = (- mtime +42) # Примечание: не `=`, а ` + =`, чтобы добавить (добавить) в массив.
фи
найти "$ start_directory" "$ {критерии [@]}"   другое_ найти _args

От Баш (1) ,

На любой элемент массива можно ссылаться с помощью . … Если есть или  , слово распространяется на всех членов . Эти индексы отличаются только тогда, когда слово появляется в двойных кавычках. Если слово в двойных кавычках,… расширяет каждый элемент до отдельного слова.${name[subscript]}subscript@*name${name[@]}name

Таким образом, "${criteria[@]}"расширяется (в приведенном выше примере) ноль, два или четыре элемента criteriaмассива, каждый в кавычках. В частности, если ни одно из условий  s не является истинным, criteriaмассив не имеет содержимого (как установлено criteria=()оператором) и ничего не "${criteria[@]}"вычисляет (даже неудобную пустую строку).


Это становится особенно интересным и сложным, когда вы имеете дело с несколькими словами, некоторые из которых представляют собой динамический (пользовательский) ввод, который вы не знаете заранее, и могут содержать пробелы или другие специальные символы. Рассмотреть возможность:

printf "Введите имя файла для поиска:"
читать имя
if ["$ fname"! = ""]
тогда
    критерии + = (- имя "$ fname")
фи

Обратите внимание, что $fnameцитируется каждый раз, когда он используется. Это работает, даже если пользователь вводит что-то вроде foo barили foo*"${criteria[@]}"оценивает -name "foo bar"или -name "foo*". (Помните, что каждый элемент массива указан в кавычках.)

Массивы не работают во всех оболочках POSIX; Массивы - это ksh / bash / zsh / yash-ism. За исключением ... есть один массив, который поддерживают все оболочки: список аргументов, иначе "$@". Если вы закончили со списком аргументов, с которым вы были вызваны (например, вы скопировали все «позиционные параметры» (аргументы) в переменные или обработали их иным образом), вы можете использовать список аргументов в виде массива:

если какое-то условие
тогда
    set - -type f # Вы можете сказать `set -" -type "" f "`, но это действительно не нужно.
еще
    установлен --
фи
если какое-то другое условие
тогда
    set - "$ @" -mtime +42
фи
# Аналогично: set - "$ @" -name "$ fname"
find "$ start_directory" "$ @"   other_ find _args

"$@"Конструкция (которая исторически пришел первым) имеет ту же семантику, - она расширяется каждый аргумент (т.е. каждый элемент списка аргументов) в отдельное слово, как если бы вы ввели ."${name[@]}""$1" "$2" "$3" …

Выдержка из спецификации языка команд оболочки POSIX , в разделе 2.5.2 Специальные параметры ,

@

    Расширяется до позиционных параметров, начиная с одного, первоначально создавая одно поле для каждого установленного позиционного параметра. …, Начальные поля должны быть сохранены как отдельные поля,…. Если позиционные параметры отсутствуют, расширение @должно генерировать нулевые поля, даже если они заключены @в двойные кавычки; ...

Полный текст несколько загадочный; Ключевым моментом является то, что он указывает, что "$@"должен генерировать нулевые поля, когда нет позиционных параметров. Историческая справка: когда "$@"он впервые был представлен в оболочке Bourne (предшественник bash) в 1979 году, в нем была ошибка, которая "$@"заменялась одной пустой строкой, когда не было позиционных параметров; Посмотрите, что ${1+"$@"}означает сценарий оболочки и чем он отличается "$@"? Традиционная семья Bourne ShellЧто ${1+"$@"}означает ... и где это необходимо? и "$@"против${1+"$@"} .


Массивы помогают и в первой ситуации:

если какое-то условие
тогда
    ignorecase = (- i) # Вы можете сказать `ignorecase = (" - i ")`, но это действительно не нужно.
еще
    ignorecase = () # Не используйте кавычки в этой команде!
фи
grep "$ {ignorecase [@]}"   other_ grep _args

____________________

PS (csh)

Это должно быть само собой разумеющимся, но для блага новых пользователей: csh, tcsh и т. Д. Не являются оболочками Bourne / POSIX. Они совсем другая семья. Лошадь другого цвета. Совсем другая игра с мячом. Другая порода кошек. Птицы другого пера. И, в частности, другая банка червей.

Часть того, что было сказано на этой странице, относится к csh; например: это хорошая идея заключать в кавычки все ваши переменные, если у вас нет веских причин не делать этого, и вы уверены, что знаете, что делаете. Но в csh каждая переменная является массивом - так уж получилось, что почти каждая переменная является массивом только из одного элемента и действует очень похоже на обычную переменную оболочки в оболочках Bourne / POSIX. И синтаксис ужасно другой (и я имею в виду ужасно ). Поэтому мы не будем больше говорить о оболочках семейства csh.


1
Обратите внимание, что в csh, вы бы предпочли использовать, а $var:qне "$var"как последний не работает для переменных, которые содержат символы новой строки (или если вы хотите заключить элементы массива в кавычки по отдельности, а не объединять их с пробелами в один аргумент).
Стефан Шазелас

В bash bitrate="--bitrate 320"работает ${bitrate:+$bitrate}и bitrate=--bitrate 128не будет работать, ${bitrate:+"$bitrate"}потому что нарушает команду. Безопасно ли использовать ${variable:+$variable}без "?
Фридо

@Freedo: я нахожу твой комментарий неясным. Мне особенно непонятно, что вы хотите знать, что я еще не сказал. См. Второй заголовок «Но что, если» - «Но что, если у меня есть переменная, которую я хочу / нужно разделить на слова?». Это ваша ситуация, и вы должны следовать там совету. Но если вы не можете (например, потому что вы используете оболочку, отличную от bash, ksh, zsh или yash, и используете  $@для чего-то другого), или вы просто отказываетесь использовать массив по другим причинам, я обращаюсь Вы «Как обсуждалось в другом ответе». … (Продолжение)
G-Man

(Продолжение) ... Ваше предложение (не используя ${variable:+$variable}при нет ") потерпит неудачу , если IFS содержит  -,  0, 2, 3, a, b, e, i, rили  t.
G-Man

Ну, моя переменная имеет символы a, e, b, i 2 и 3 и все еще отлично работает${bitrate:+$bitrate}
Freedo

11

Я скептически отнесся к ответу Стефана, однако можно злоупотреблять $#:

$ set `seq 101`

$ IFS=0

$ echo $#
1 1

или $?

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1

Это надуманные примеры, но потенциал существует.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.