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

Если Вы загружаете исходный код (т.е. не пакеты от Вашего распределения) он будет обычно содержать настраивать сценарий, который позволяет Вам указывать, где установить скомпилированное программное обеспечение. Это обычно значения по умолчанию к /usr/local. Можно изменить это при помощи следующей опции:

$ ./configure --prefix=/foo

Не все программное обеспечение сделано этот путь, но необычно не быть. Так как Ваше распределение устанавливает программное обеспечение под/usr, это означает, что у Вас будет две версии в Вашей системе. При установке ffmpeg на/opt/ffmpeg необходимо было бы просто добавить двоичный каталог (вероятно,/opt/ffmpeg/bin) к ПУТИ.

Если Вы действительно интересуетесь источником, смотрите на Linux С нуля и хинду.

293
02.05.2016, 03:52
5 ответов
[1177366] Всегда используйте двойные кавычки вокруг подстановок переменных и подстановок команд: [1177955] "$foo"[1177956], [1177957] "$(foo)"[12174] Если вы используете [1177959]$foo[1177960] без кавычек, ваш скрипт подавится входом или параметрами (или выводом команды, при этом [1177961]$(foo)[1177962]), содержащими пробелы или [1177963] \[*?[1177964].[12175] Там вы можете перестать читать. Ну, хорошо, вот еще несколько:[12176]read[1178220] - [1178221]Для чтения входных строк за строкой со строкой [1178279]read[1178280] use [1178281] while IFS= read -r line; do ...[12177]Plain [1178224]read[1178225] treats backslashes and whitespace special. [12178]xargs[1178227] - [1178228] Избегайте [1178283]xargs[12179]. Если необходимо использовать [1178230]xargs[1178231], сделайте это [1178232]xargs -0[1178233]. Вместо [1178234] найдите ... | xargs[1178235], [1178236] предпочтителено [1178285] найти ... -exec ...[12180]. [12181]xargs[1178240] обрабатывает пробельные символы и символы [1178241]\"'[1178242] специально. [12182] Этот ответ применим к ракушкам в стиле Борна/POSIX ([1177969]sh[1177970], [1177971]ясень[1177972], [1177973]тире[1177974], [1177975]баш[1177976], [1177977]кш[1177978], [1177979]мкш[1177980], [1177981]яш[1177982]...). Пользователи Zsh должны пропустить его и прочитать конец [1177983] Когда необходимо двойное цитирование?[1177984] вместо этого. Если вам нужен полный нитти-гритти, [1177985] прочтите стандарт[1177986] или руководство по работе с оболочкой.[12183] Обратите внимание, что пояснения ниже содержат несколько аппроксимаций (утверждения, которые верны в большинстве условий, но на них может влиять окружающий контекст или конфигурация).[12184] Зачем мне нужно писать [1177987] "$foo"[1177988]? Что происходит без кавычек?[12185]$foo[1177990] не означает "взять значение переменной [1177991]foo[1177992]". Это значит нечто гораздо более сложное:[12186]Во-первых, возьмите значение переменной.[12187]Разбиение полей: относитесь к этому значению, как к разделенному пробелами списку полей, и построите полученный список. Например, если переменная содержит [1178243]foo * bar [1178244], то результатом этого шага будет 3-элементный список [1178245]foo[1178246], [1178247]*[1178248], [1178249]bar[1178250].[12188]Генерация имени файла: относитесь к каждому полю как к глобусу, т.е. как к шаблону подстановки, и замените его на список имен файлов, которые соответствуют этому шаблону. Если шаблон не соответствует ни одному файлу, он остается неизменным. В нашем примере это приводит к тому, что список, содержащий [1178251]foo[1178252], следует за списком файлов в текущем каталоге, и, наконец, [1178253]bar[1178254]. Если текущий каталог пуст, то результатом будет [1178255]foo[1178256], [1178257]*[1178258], [1178259]bar[1178260].[12189]Обратите внимание, что в результате получится список строк. В синтаксисе оболочки есть два контекста: список контекста и строковый контекст. Разделение полей и генерация имен файлов происходят только в контексте списка, но в большинстве случаев. Двойные кавычки разделяют контекст строки: вся строка, заключенная в двойные кавычки, является единственной строкой, не подлежащей разделению. (Исключение: [1177999] "$@"[1178000] для расширения до списка позиционных параметров, например, [1178001] "$@"[1178002] эквивалентно [1178003] "$1" "$2" "$3"[1178004], если есть три позиционных параметра. Смотрите [1178005] Какая разница между $* и $@?[1178006])[12190] То же самое происходит с заменой команды на [1178007]$(foo)[1178008] или на [1178009]`foo`[1178010]. На заметке, не используйте [1178011]`foo`[1178012]: его правила кавычки странные и не портативные, а все современные оболочки поддерживают [1178013]$(foo)[1178014], что абсолютно эквивалентно, за исключением интуитивно понятных правил кавычки. [12191]Вывод арифметической подстановки также подвергается тем же расширениям, но это обычно не относится к делу, так как содержит только нерасширяемые символы (предполагая, что [1178015]IFS[1178016] не содержит цифр или [1178017]-[1178018]). [12192]Смотрите [1178019]Когда необходима двойная кавычка?[1178020] для более подробной информации о случаях, когда можно пропустить кавычки.[12193]Если вы не хотите, чтобы случилась вся эта ригмарола, просто не забывайте всегда использовать двойные кавычки вокруг подстановок переменных и команд. Будьте внимательны: пропуски кавычек могут привести не только к ошибкам, но и к [1178021]дырам в безопасности[1178022].[12194]Как обработать список имен файлов?[12195]Если вы пишете [1178023]myfiles="file1 file2"[1178024], с пробелами для разделения файлов, то это не может работать с именами файлов, содержащими пробелы. Имена Unix-файлов могут содержать любые символы, кроме [1178025]/[1178026] (который всегда является разделителем каталогов) и нулевых байтов (которые нельзя использовать в скриптах shell'ов с большинством shell'ов)[12196]Та же проблема с [1178027]myfiles=*.txt; ... обрабатывать $myfiles[1178028]. При этом переменная [1178029]myfiles[1178030] содержит 5-символьную строку [1178031]*.txt[1178032], и именно при записи [1178033]$myfiles[1178034] расширяется подстановочный символ. Этот пример на самом деле будет работать, пока вы не измените свой скрипт на [1178035]myfiles="$someprefix*.txt"; ... обработайте $myfiles[1178036]. Если [1178037]someprefix[1178038] установлен в [1178039]итоговый отчет[1178040], то это не сработает.[12197]Чтобы обработать список любого вида (например, имена файлов), поместите его в массив. Для этого требуется mksh, ksh93, yash или bash (или zsh, который не имеет всех этих проблем с кавычками); простая POSIX-оболочка (такая как ash или dash) не имеет переменных массива.[12198]Ksh88 имеет переменные массива с другим синтаксисом присваивания [1178041]set -A myfiles "someprefix "*.txt[1178042] (см. [1178043]assignment variable under different ksh environment[1178044], если вам нужна переносимость ksh88/bash). Оболочки Bourne/POSIX-стиля имеют один массив, массив позиционных параметров [1178045]"$@"[1178046], который вы устанавливаете с помощью [1178047]set[1178048] и который является локальным для функции:[12199]А как насчет имен файлов, которые начинаются с [1178049]-[1178050]? [12200]Помните, что имена файлов могут начинаться с [1178051]-[1178052] (тире/минусе), который большинство команд интерпретирует как обозначение опции. Некоторые команды (например, [1178053]sh[1178054], [1178055] set[1178056] или [1178057]sort[1178058]) также принимают опции, которые начинаются с [1178059]+[1178060]. Если имя файла начинается с переменной части, обязательно передайте [1178061]--[1178062] перед ней, как в приведенном выше фрагменте. Это указывает команде на то, что она достигла конца опций, поэтому все, что после этого будет именем файла, даже если оно начинается с [1178063]-[1178064] или [1178065]+[1178066].[12201]В качестве альтернативы, вы можете убедиться, что имена файлов начинаются с символа, отличного от [1178067]-[1178068]. Абсолютные имена файлов начинаются с [1178069]/[1178070], и вы можете добавить [1178071]./[1178072] в начале относительных имен. Следующий фрагмент превращает содержимое переменной [1178073]f[1178074] в "безопасный" способ обращения к тому же самому файлу, который гарантированно не начинается ни с [1178075]-[1178076], ни с [1178077]+[1178078].[12202]В заключительной заметке на эту тему остерегайтесь, что некоторые команды интерпретируют [1178079]-[1178080] как значение стандартного входа или стандартного выхода, даже после [1178081]--[1178082]. Если вам нужно обратиться к реальному файлу с именем [1178083]-[1178084], или если вы вызываете такую программу и не хотите, чтобы она читала из stdin или записывала в stdout, обязательно перепишите [1178085]-[1178086], как описано выше. Смотрите [1178087]-[1178086] Какова разница между "du -sh *" и "du -sh ./*"?[1178088] для дальнейшего обсуждения.[12203]Как хранить команду в переменной?[12204] "Команда" может означать три вещи: имя команды (имя как исполняемого файла, с или без полного пути, или имя функции, builtin или псевдоним), имя команды с аргументами или часть кода оболочки. Соответственно, существуют различные способы хранения их в переменной.[12205]Если у вас есть имя команды, Просто сохраните ее и используйте переменную в двойных кавычках, как обычно.[12206]Если у вас есть команда с аргументами, то проблема такая же, как и со списком имён файлов выше: это список строк, а не строка. Вы не можете просто запихивать аргументы в одну строку с пробелами между ними, потому что если вы это сделаете, то не сможете различить пробелы, которые являются частью аргументов, и пробелы, которые разделяют аргументы. Если в вашем shell'е есть массивы, вы можете их использовать.[12207]Что, если вы используете shell без массивов? Вы все равно можете использовать позиционные параметры, если не возражаете против их изменения.[12208]Что, если вам нужно сохранить сложную команду оболочки, например, с перенаправлением, трубами и т.д.? Или если вы не хотите изменять позиционные параметры? Тогда вы можете построить строку, содержащую команду, и использовать [1178089]eval[1178090] builtin. [12209]Обратите внимание на вложенные кавычки в определении кода [1178091][1178092]: одиночные кавычки [1178093]'...'[1178094] разделяют строковый литерал, так что значением переменной [1178095]code[1178096] является строка [1178097]/путь/к/исполняемому --option --message="hello world" -- /путь/к/файлу1[1178098]. Сборка [1178099]eval[1178100] говорит оболочке разобрать передаваемую строку в качестве аргумента, как если бы она появилась в скрипте, поэтому в этот момент кавычки и трубка разобраны и т.д.[12210] Использовать [1178101]eval[1178102] непросто. Подумайте хорошенько о том, когда будет происходить синтаксический разбор. В частности, нельзя просто записать имя файла в код: нужно процитировать его так же, как если бы он был в файле с исходным кодом. Нет прямого способа сделать это. Что-то вроде [1178103] code="$code $filename"[1178104] ломается, если имя файла содержит какой-либо специальный символ оболочки (пробелы, [1178105]$[1178106], [1178107];[1178108], [1178109] |[1178110], [1178111]<[1178112], [1178113]>[1178114] и т.д.). [1178115] code="$код \ "$filename\""[1178116] по-прежнему ломается на [1178117] "$\`[1178118]. Даже [1178119]code="$код '$filename'"[1178120] ломается, если имя файла содержит [1178121]'[1178122]. Есть два решения.[12211]Добавьте слой кавычек вокруг имени файла. Самый простой способ сделать это - добавить вокруг него одинарные кавычки и заменить одинарные кавычки на [1178287]'\''[1178288].[12212]quoted_filename=$(printf %s. "$filename" | sed "s/'/'\\\\''/g"). code="$код '${quoted_filename%.}'". [12213]Сохранить расширение переменной внутри кода так, чтобы оно выглядело при оценке кода, а не при сборке фрагмента кода. Это проще, но работает только в том случае, если переменная на момент выполнения кода все еще находится рядом с тем же самым значением, а не, например, если код собирается в цикле.[12214]code="$code \"\$filename\"". [12215] Наконец-то, тебе действительно нужна переменная, содержащая код? Самый естественный способ дать имя блоку кода - это определить функцию.[12216]Что случилось с чтением [1178127][1178128]? [12217]Без [1178129]-r[1178130], [1178131]read[1178132] позволяет продолжить строку - это одна логическая строка ввода:[12218]read[1178134] разбивает строку ввода на поля, разделенные символами в [1178135]$IFS[1178136] (без [1178137]-r[1178138], обратный слеш также экранирует их). Например, если на входе строка, содержащая три слова, то [1178139] прочитанная первая третья [1178140] устанавливает [1178141] первое [1178142] к первому слову на входе, [1178143] второе [1178144] ко второму слову и [1178145]третье [1178146] к третьему слову. Если слов больше, то последняя переменная содержит все, что осталось после установки предыдущих. При установке [1178147]IFS[1178148] в пустую строку обрезка пробельных символов и трейлинговых пробельных символов исключается. Смотрите [1178149]Почему `в то время как IFS= read` используется так часто, вместо `IFS=; в то время как read...`?`[1178150] для более подробного объяснения.[12220]Что не так с [1178151]xargs[1178152]?[12221]Входной формат [1178153]xargs[1178154] - это строки, разделенные пробелами, которые по желанию могут быть одно- или двухцветными. Ни один стандартный инструмент не выводит этот формат.[12222]Вход в [1178155]xargs -L1[1178156] или [1178157]xargs -l[1178158] - это почти список строк, но не совсем - если в конце строки есть пробел, то следующая строка является строкой продолжения.[12223]Вы можете использовать [1178159]xargs -0[1178160] там, где это применимо (и там, где это возможно): GNU (Linux, Cygwin), BusyBox, BSD, OSX, но его нет в POSIX). Это безопасно, потому что нулевые байты не могут появиться в большинстве данных, в частности, в именах файлов. Для создания нуль-разделенного списка имен файлов используйте [1178161]find ... -print0[1178162] (или вы можете использовать [1178163]find ... -exec ...[1178164], как описано ниже).[12224]Как обрабатывать файлы, найденные по [1178165]find[1178166]?[12225]some_command[1178168] должна быть внешняя команда, это не может быть функция оболочки или псевдоним. Если вам нужно вызвать оболочку для обработки файлов, откровенно вызовите [1178169]sh[1178170].[12226]У меня есть еще один вопрос [12227] Просмотрите на этом сайте тег [1178171]цитирующий[1178172], или [1178173]shell[1178174], или [1178175]shell-script[1178176]. (Нажмите на "узнать больше...", чтобы увидеть некоторые общие советы и выбранный вручную список общих вопросов). Если вы искали и не можете найти ответ, [1178177] спросите [1178178].[1177456].
362
27.01.2020, 19:26
[1178389]Несмотря на то, что ответ Жиля отличный, я берусь за главный вопросВсегда используйте двойные кавычки вокруг подстановок переменных и команды замены: "$foo", "$(foo)"Когда вы начинаете с Бэш-подобной оболочки, которая делает разделение слов, да из Конечно, безопасный совет всегда использовать цитаты. Однако, разделение слов - это не всегда выполняется

§ Word Splitting Эти команды могут быть выполнены без ошибок

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

26
27.01.2020, 19:26

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

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

Оболочка по своей сути и душе (для тех, у кого они есть) является интерпретатором команд - это синтаксический анализатор, подобный большому интерактивному sed . Если ваш оператор оболочки подавляет пробелами или что-то подобное, то это очень вероятно, потому что вы не полностью поняли процесс интерпретации оболочки - особенно, как и почему он переводит оператор ввода в действенный команда. Задача оболочки состоит в следующем:

  1. принять ввод

  2. интерпретировать и правильно разделить на токенизированный ввод слова

    • ввод слова являются элементами синтаксиса оболочки, такими как $ word или echo $ words 3 4 * 5

    • слов всегда разделяются на пробелы - это всего лишь синтаксис - но только буквальные символы пробелов передаются оболочке во входном файле

  3. при необходимости разверните их в несколько полей

    • поля являются результатом расширений слова - они составляют последнюю исполняемую команду

    • , за исключением «$ @» , $ IFS разделение полей и расширение имени пути входное слово всегда должно оцениваться как одно поле .

  4. , а затем для выполнения полученной команды

    • в большинстве случаев это включает передачу результатов ее интерпретации в той или иной форме

Люди часто говорят, что оболочка представляет собой клей , и, если это правда, то это прикрепление списков аргументов - или полей - к тому или иному процессу, когда он exec их. Большинство оболочек плохо обрабатывают байт NUL - если вообще обрабатывают - и это потому, что они уже разбивают его. Оболочка должна exec много , и она должна делать это с помощью массива аргументов с разделителями NUL , которые она передает ядру системы во время exec . . Если бы вы смешали разделитель оболочки с данными с разделителями, оболочка, вероятно, все испортила. Его внутренние структуры данных, как и большинство программ, полагаются на этот разделитель. В частности, zsh не облажается.

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

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

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

slashes=///// spaces='     '
IFS=/; printf '<%s>' $slashes$spaces
<><><><><><     >
IFS=' '; printf '<%s>' $slashes$spaces
</////>
IFS=; printf '<%s>' $slashes$spaces
</////     >
unset IFS; printf '<%s>' "$slashes$spaces"
</////     >

Но это всего лишь $ IFS - просто разделение слов или пробел , как просили, так что насчет специальных символов ?

Оболочка - по умолчанию - также расширяет некоторые маркеры без кавычек (например, ? * [, как указано в другом месте здесь) в несколько полей , когда они встречаются в списке. Это называется раскрытием имени пути или подстановкой . Это невероятно полезный инструмент, и, поскольку это происходит после разделения полей в порядке синтаксического анализа оболочки, на него не влияют $ IFS - поля сгенерированные расширением имени пути ограничиваются в начале / конце самих имен файлов, независимо от того, содержат ли их содержимое какие-либо символы в настоящее время в $ IFS . Это поведение включено по умолчанию, но в противном случае его очень легко настроить.

set -f

Это указывает оболочке , а не на glob . Расширение имени пути не произойдет, по крайней мере, до тех пор, пока этот параметр не будет каким-либо образом отменен - ​​например, если текущая оболочка будет заменена другим новым процессом оболочки или ....

set +f

... будет передано оболочке. Двойные кавычки - как и для $ IFS field-splitting - делают этот глобальный параметр ненужным для каждого раскрытия. Итак:

echo "*" *

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

set -f; echo "*" *

... результаты для обоих аргументов идентичны - * в этом случае не расширяется.

22
27.01.2020, 19:26

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

$ FILES='"a b" c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
$ FILES='a\ b c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
0
20.08.2021, 12:51

Метод использования find directory -print0 | xargs -0должен обрабатывать все спец. Однако для каждого файла/каталога требуется один PID, что может привести к проблемам с производительностью.

Позвольте мне описать другой метод надежной (и производительной )обработки файлов, с которым я недавно столкнулся, который подходит, если findвывод должен обрабатываться после -как вкладка -разделенные данные CSV, например по АВК. При такой обработке нарушают работу только табуляции и символы новой строки в именах файлов :

.

Каталог сканируется через find directory -printf '%P\t///\n'. Если путь не содержит табуляции или новой строки,это приводит к одной записи с двумя полями CSV :сам путь и поле, содержащее ///.

Если в пути содержится вкладка, будет три поля :фрагмент пути1, фрагмент пути2 и поле, содержащее ///.

Если есть новая строка, будет две записи :первая запись будет содержать фрагмент пути1, а вторая запись будет содержать фрагмент2 пути и поле, содержащее ///.

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

Также можно написать программу (AWK ), которая просматривает findвывод и, пока не найдет ///, соединяет фрагменты вместе, зная, что новое поле это вкладка в пути, а новая запись - это новая строка в пути.

Вкладки можно безопасно экранировать как ///t, а новые строки можно безопасно экранировать как ///n, опять же, зная, что ///не может естественным образом встречаться в путях к файлам. Преобразование ///tи ///nобратно во табуляцию и новую строку может произойти в конце, когда в результате обработки генерируется некоторый вывод.

Да, это звучит сложно, но разгадка в том, что нужны только два PID :экземпляр findи awk, которые выполняют описанный алгоритм. И это быстро.

Идея не моя, я нашел ее реализованной в этом новом (2019 )скрипте bash для синхронизации каталогов:Zaloha.sh . На самом деле у них есть документ, который описывает алгоритм.

Мне не удалось сломать/задушить эту программу специальными символами в именах файлов. Он даже правильно обрабатывал каталоги с названиями новой строки и табуляции...

0
20.08.2021, 12:51

Теги

Похожие вопросы