Вплив на безпеку забуття процитувати змінну в оболонках bash / POSIX


206

Якщо ви деякий час стежите за unix.stackexchange.com, ви, мабуть, сподіваєтесь, що до цього часу знаєте, що залишення змінної без котирування у контексті списку (як у echo $var) в оболонках Bourne / POSIX (zsh є винятком) має дуже особливе значення і не слід робити, якщо у вас є дуже вагомі причини.

Це обговорюється в ряді Q & A тут (приклади: Я зареєстрував заслінку сценарій оболонки на пробіл або інші спеціальні символи , Коли подвійні лапки необхідно? , Розширення змінної оболонки і ефект Глоб і розколу на ньому , Цитується проти нецитованого розширення рядків)

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

Зараз ми все ще бачимо низку відповідей і навіть періодично публічно випускаємо код оболонки, де змінні не цитуються. Ви могли б подумати, що люди вже б дізналися.

З мого досвіду, в основному є 3 типи людей, які пропускають цитувати свої змінні:

  • новачки. Це можна виправдати, як правда, це абсолютно неінтуїтивний синтаксис. І наша роль на цьому веб-сайті навчати їх.

  • забудькуваті люди.

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

Можливо, ми можемо переконати їх, якщо піддаємо ризик, пов’язаний з таким видом поведінки.

Що найгірше, ніж це може статися, якщо ви забудете цитувати свої змінні. Чи справді це , що погано?

Про яку вразливість ми говоримо тут?

У яких контекстах це може бути проблемою?


8
Я думаю, BashPitfalls - це те, що тобі сподобається.
pawel7318

зворотну посилання з цієї статті, яку я написав , дякую за запис
mirabilos

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

Відповіді:


201

Преамбула

По-перше, я б сказав, що це не правильний спосіб вирішення проблеми. Це трохи схоже на те, щоб сказати: " не слід вбивати людей, бо інакше ти потрапиш до в'язниці ".

Так само ви не цитуєте змінну, оскільки в іншому випадку ви вводите вразливі місця безпеки. Ви цитуєте свої змінні, оскільки це неправильно не робити (але якщо страх перед в'язницею може допомогти, чому б і ні).

Невелике резюме для тих, хто щойно заскочив у поїзд.

У більшості оболонок залишення змінної розширення без котирування (хоча це (і решта цієї відповіді) також стосується підстановки команд ( `...`або $(...)), а арифметичне розширення ( $((...))або $[...])) має дуже особливе значення. Найкращий спосіб описати це, як це викликати якийсь неявний спліт + глобальний оператор¹.

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, або що, на щастя, більшість систем не дозволяють встановити / встановити жорсткі сценарії оболонок в даний час, одна річ, що дозволяє скористатися обробкою оболонок заголовки в вересні 2014 г.) показали, що шкаралупа по - , як і раніше широко використовується там , де вони , ймовірно , не слід: у CGIs, в DHCP - клієнт скрипти пасток, в sudoers команд, що викликаються з допомогою (якщо не як ) Setuid команди ...

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

У вас є вразливість, коли є шлях до ескалації привілеїв, тобто коли хтось (давайте назвемо його зловмисником ) здатний зробити щось, чого йому не призначено.

Це незмінно означає, що зловмисник надає дані, що дані обробляються привілейованим користувачем / процесом, який ненавмисно робить щось, чого не повинен робити, в більшості випадків через помилку.

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

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

Що стосується змінних, то у випадку сценарію CGI - це цілком очевидно, що дані - це параметри CGI GET / POST і такі речі, як файли cookie, шлях, хост ... параметри.

Для встановленого сценарію (працює як один користувач, коли викликається іншим), це аргументи або змінні середовища.

Ще один дуже поширений вектор - це імена файлів. Якщо ви отримуєте список файлів з каталогу, можливо, файли там посадили зловмисники .

У зв'язку з цим , навіть в рядку інтерактивної оболонки, ви могли б бути уразливим (при обробці файлів в /tmpабо ~/tmp , наприклад).

Навіть ~/.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}

:- команда без операції. Що може піти не так?

Це означало , щоб призначити $1для $QUERYSTRINGякщо $QUERYSTRING було відключено. Це швидкий спосіб зробити CGI-скрипт, який можна дзвонити з командного рядка.

Це $QUERYSTRINGвсе ще розширено, і тому що це не цитується, викликається оператор split + glob .

Зараз є деякі глобуси, які особливо дорого розширюються. /*/*/*/*Один досить погано , як це означає , що лістинг каталогів до 4 -х рівнів вниз. На додаток до активності диска та процесора, це означає зберігання десятків тисяч файлових шляхів (40 к тут на мінімальному сервері VM, 10 к. З яких каталоги).

Зараз це /*/*/*/*/../../../../*/*/*/*означає 40k x 10k і /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*цілком достатньо, щоб поставити на коліна навіть найсильнішу машину.

Спробуйте самі (хоча будьте готові до того, що ваша машина вийде з ладу або зависне):

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

Звичайно, якщо код:

echo $QUERYSTRING > /some/file

Потім ви можете засипати диск.

Просто виконайте пошук Google на оболонках cgi або bash cgi або ksh cgi , і ви знайдете кілька сторінок, які показують, як писати CGI в оболонках. Зверніть увагу, наскільки половина з тих, хто параметри процесу є вразливими.

Навіть сам Девід Корн вразливий (дивіться на обробку файлів cookie).

аж до довільних уразливостей виконання коду

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

Це взагалі розділена частина, яка призводить до цих. Це розщеплення призводить до декількох аргументів, які передаються командам, коли очікується лише один. У той час як перші з них будуть використовуватися в очікуваному контексті, а інші будуть в іншому контексті, тому потенційно трактуються по-різному. Краще з прикладом:

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або zshs [або 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, давайте попросимо оболонку розділити + 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значенні a (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 ]]може повернути істину у випадках, коли $aвони відрізняються від $b(наприклад, коли $aє anythingі $bє *), або можуть повернути помилкові, коли вони однакові (наприклад, коли вони є $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

Там не буде ніякого розколу + Glob , але якщо $varпорожньо, замість того щоб отримувати один порожній аргумент, cmdне отримають ніяких аргументів взагалі.

Це може спричинити помилки (як очевидні [ -n $var ]). Це, можливо, може порушити очікування та припущення сценарію та спричинити вразливості, але я зараз не можу придумати не надто надуманий приклад).

А коли ви робите потрібен розкол + Glob оператор?

Так, це зазвичай, коли ви хочете залишити свою змінну без котирування. Але тоді вам потрібно переконатися, що ви правильно настроїли своїх розділених та глобальних операторів, перш ніж використовувати їх. Якщо ви хочете лише розділити частину, а не частину глобуса (що трапляється більшу частину часу), вам потрібно вимкнути глобулінг ( set -o noglob/ set -f) та виправити $IFS. Інакше ви також спричините вразливості (як, наприклад, згаданий вище приклад CGI Девіда Корна).

Висновок

Коротше кажучи, залишити змінну (або заміну команд, або арифметичне розширення) цитатами в оболонках може бути дуже небезпечно, особливо якщо це зроблено в неправильних контекстах, і дуже важко зрозуміти, які це неправильні контексти.

Це одна з причин, чому це вважається поганою практикою .

Дякуємо за прочитане. Якщо це пройде над вашою головою, не хвилюйтесь. Не можна очікувати, що всі зрозуміють усі наслідки написання коду так, як вони його пишуть. Ось чому ми маємо рекомендації щодо гарної практики , тому їх можна дотримуватися, не обов'язково розуміючи, чому.

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

І будь ласка, цитуйте свої змінні у своїх відповідях на цьому сайті!


¹В ksh93і pdkshта похідних, розширення дужок також виконується, якщо не вимкнено ksh93глобалізацію (у випадку версій до ksh93u +, навіть коли braceexpandпараметр вимкнено).


Зауважимо, що, [[цитуючи, потрібно лише цитувати if [[ $1 = "$2" ]]; then
РЗС

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

1
Так, ви можете там цитувати за замовчуванням. Будь ласка, більше жодних помилок щодо розбиття поля зараз, я все-таки повинен виправити ті, про кого я знаю (від вас та інших), перш ніж ми зможемо це повторно оцінити.
mirabilos

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

3
Я не фахівець з баштів, але кодую в ньому вже більше десяти років. Я багато використовував котирування, але в основному для обробки вбудованих заготовок. Зараз я буду використовувати їх набагато більше! Було б добре, якби хтось розширив цю відповідь, щоб було легше засвоїти всі прекрасні моменти. Я отримав це багато, але я теж дуже багато пропустив. Це вже довгий пост, але я знаю, що тут мені ще багато чого навчитися. Дякую!
Джо

34

[Натхненний ця відповідь по КАСУ .]

Але що робити, якщо…?

Але що робити, якщо мій сценарій встановлює змінну до відомого значення перед його використанням? Зокрема, що робити, якщо вона встановлює змінну на одне з двох або більше можливих значень (але вона завжди встановлює її на щось відоме), і жодне з значень не містить символів пробілу чи глобуса? Чи не безпечно використовувати його без лапок у такому випадку ?

А що робити, якщо одним із можливих значень є порожній рядок, і я залежно від "видалення порожнього"? Тобто, якщо змінна містить порожню рядок, я не хочу отримувати порожню рядок у своїй команді; Я нічого не хочу отримати. Наприклад,

якщо деякий_умова
тоді
    ignorecase = "- i"
ще
    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"
ще
    критерії = ""
фі
якщо деякий_інший_умова
тоді
    Критерії = "$ критерії -міть +42"
фі
знайти "$ start_directory" $ критерії   other_ find _args

Відповідь:

Ви можете подумати, що це справа для використання eval.  Немає!   Втримайтеся від спокуси навіть думати про використання evalтут.

Знову ж таки, якщо ви переконалися, що IFSу вашій змінній немає жодного символу (за винятком пробілів, які ви хочете відзначити), і ви впевнені, що ваша змінна не містить жодних глобальних символів, то, напевно, зазначене вище безпечний.

Але якщо ви використовуєте bash (або ksh, zsh або yash), є безпечніший спосіб: використовуйте масив:

якщо деякий_умова
тоді
    Критерії = (- тип f) # Можна сказати `критерии = (" - тип "" f ")`, але це дійсно непотрібно.
ще
    Критерії = () # Не використовуйте жодних лапок для цієї команди!
фі
якщо деякий_інший_умова
тоді
    критерії + = (- mtime +42) # Примітка: не `=`, а ` + =`, щоб додати (додати) до масиву.
фі
знайти "$ start_directory" "$ {критерії [@]}"   other_ find _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 "`, але це дійсно непотрібно.
ще
    набір -
фі
якщо деякий_інший_умова
тоді
    набір - "$ @" -міть +42
фі
# Аналогічно: set - "$ @" -name "$ fname"
знайти "$ start_directory" "$ @"   other_ find _args

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

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

@

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

Повний текст дещо криптовалютний; Ключовим моментом є те, що він вказує, що "$@"генеруватимуть нульові поля, коли немає позиційних параметрів. Історична примітка: коли "$@"вперше був введений в оболонку Борна (попередник bash) у 1979 році, у неї з'явилася помилка, яку "$@"замінила одна порожня рядок, коли не було позиційних параметрів; див. Що ${1+"$@"}означає сценарій оболонки і чим він відрізняється від "$@"? Традиційна родина шкаралуп Борнащо ${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.