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

285

Или вводное руководство по надежной обработке имени файла и другой передаче строк в сценариях оболочки.

Я написал сценарий оболочки, который работает хорошо большую часть времени. Но он душит некоторые входные данные (например, некоторые имена файлов).

Я столкнулся с такой проблемой, как:

  • У меня есть имя файла, содержащее пробел hello world, и оно рассматривалось как два отдельных файла helloи world.
  • У меня есть строка ввода с двумя последовательными пробелами, и они сократились до одного на входе.
  • Начальные и конечные пробелы исчезают из строк ввода.
  • Иногда, когда вход содержит один из символов \[*?, они заменяются неким текстом, который на самом деле является именем файла.
  • Во входных данных есть апостроф '(или двойная кавычка "), и после этого все становится странным.
  • Во входных данных есть обратная косая черта (или: я использую Cygwin, и некоторые из моих имен файлов имеют \разделители в стиле Windows ).

Что происходит и как мне это исправить?

жилль
источник
16
shellcheckпомочь вам улучшить качество ваших программ.
Аурелиен
3
Помимо защитных методов, описанных в ответах, и хотя это, вероятно, очевидно для большинства читателей, я думаю, что стоит прокомментировать, что, когда файлы предназначены для обработки с использованием инструментов командной строки, рекомендуется избегать использования причудливых символов в имена в первую очередь, если это возможно.
ло
1
@bli Нет, это заставляет только багов дольше появляться. Это скрывает ошибки сегодня. И теперь, вы не знаете всех имен файлов, которые позже используются с вашим кодом.
Фолькер Сигел
Прежде всего, если ваши параметры содержат пробелы, они должны быть заключены в кавычки (в командной строке). Однако вы можете взять всю командную строку и разобрать ее самостоятельно. Два пробела не превращаются в один пробел; Любое количество места сообщает вашему сценарию, что это следующая переменная, поэтому, если вы делаете что-то вроде «echo $ 1 $ 2», это ваш сценарий, помещающий один пробел между ними. Также используйте «find (-exec)» для перебора файлов с пробелами вместо цикла for; Вы можете справиться с пробелами легче.
Патрик Тейлор

Ответы:

352

Всегда используйте двойные кавычки подстановок переменных и команд замен: "$foo","$(foo)"

Если вы используете без $fooкавычек, ваш сценарий захлебнется вводом или параметрами (или выводом команды, с $(foo)), содержащими пробел или \[*?.

Там вы можете перестать читать. Ну, хорошо, вот еще несколько:

  • read- Чтобы читать входные данные построчно с помощью readвстроенной функции, используйтеwhile IFS= read -r line; do …
    Plain, чтобы readобрабатывать обратную косую черту и пробелы специально.
  • xargs- Избегайтеxargs . Если вы должны использовать xargs, сделайте это xargs -0. Вместо того find … | xargs, чтобы предпочестьfind … -exec … .
    xargsобращается с пробелами и персонажами \"'специально.

Этот ответ относится к оболочкам Bourne / POSIX-стиле ( sh, ash, dash, bash, ksh, mksh, yash...). Пользователи Zsh должны пропустить это и прочитать конец. Когда необходимо двойное цитирование? вместо. Если вы хотите получить все, что нужно, читайте стандарт или руководство по вашей оболочке.


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

Зачем мне писать "$foo"? Что происходит без кавычек?

$fooне означает «принять значение переменной foo». Это означает что-то гораздо более сложное:

  • Сначала возьмите значение переменной.
  • Разделение полей: обработайте это значение как список полей, разделенных пробелами, и создайте получившийся список. Например, если переменная содержит foo * bar ​то результатом этого шага является список 3-элемент foo, *, bar.
  • Генерация имени файла: обрабатывайте каждое поле как глобус, то есть как шаблон с подстановочными знаками, и заменяйте его списком имен файлов, соответствующих этому шаблону. Если шаблон не соответствует ни одному файлу, он остается неизменным. В нашем примере это приводит к списку, который содержит fooсписок файлов в текущем каталоге и, наконец, список bar. Если текущий каталог пуст, результат foo, *, bar.

Обратите внимание, что результатом является список строк. В синтаксисе оболочки есть два контекста: контекст списка и строковый контекст. Разделение полей и генерация файлов происходят только в контексте списка, но это происходит в большинстве случаев. Двойные кавычки отделяют строковый контекст: вся строка в двойных кавычках представляет собой одну строку, которую нельзя разделять. (Исключение: "$@"расширение до списка позиционных параметров, например "$@", эквивалентно, "$1" "$2" "$3"если есть три позиционных параметра. См. В чем разница между $ * и $ @? )

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

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

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

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

Как мне обработать список имен файлов?

Если вы пишете myfiles="file1 file2"с пробелами для разделения файлов, это не может работать с именами файлов, содержащими пробелы. Имена файлов Unix могут содержать любой символ, кроме /(который всегда является разделителем каталогов) и нулевых байтов (которые нельзя использовать в сценариях оболочки с большинством оболочек).

Та же проблема с myfiles=*.txt; … process $myfiles. Когда вы делаете это, переменная myfilesсодержит 5-символьную строку *.txt, и когда вы пишете $myfiles, подстановочный знак раскрывается. Этот пример будет работать, пока вы не измените свой сценарий на myfiles="$someprefix*.txt"; … process $myfiles. Если someprefixустановлено значение final report, это не будет работать.

Чтобы обработать список любого типа (например, имена файлов), поместите его в массив. Для этого требуется mksh, ksh93, yash или bash (или zsh, у которого нет всех этих проблем с цитированием); простая оболочка POSIX (например, ash или dash) не имеет переменных массива.

myfiles=("$someprefix"*.txt)
process "${myfiles[@]}"

Ksh88 имеет переменные-массивы с другим синтаксисом присваивания set -A myfiles "someprefix"*.txt(смотрите переменную присваивания в другой среде ksh, если вам нужна переносимость ksh88 / bash). Оболочки в стиле Bourne / POSIX имеют единый массив - массив позиционных параметров, "$@"который вы устанавливаете setи который является локальным для функции:

set -- "$someprefix"*.txt
process -- "$@"

Как насчет имен файлов, которые начинаются с -?

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

Кроме того, вы можете убедиться, что имена ваших файлов начинаются с символа, отличного от -. Абсолютные имена файлов начинаются с /, и вы можете добавить ./в начале относительные имена. Следующий фрагмент кода превращает содержимое переменной fв «безопасный» способ ссылки на тот же файл, с которого гарантированно не начинаться -.

case "$f" in -*) "f=./$f";; esac

Последнее замечание по этой теме, помните, что некоторые команды интерпретируют -как означающие стандартный ввод или стандартный вывод, даже после --. Если вам нужно сослаться на фактический файл с именем -или если вы вызываете такую ​​программу и не хотите, чтобы она читала из stdin или записывала в stdout, не забудьте переписать, -как указано выше. См. В чем разница между "du -sh *" и "du -sh ./*"? для дальнейшего обсуждения.

Как мне сохранить команду в переменной?

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

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

command_path="$1"

"$command_path" --option --message="hello world"

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

cmd=(/path/to/executable --option --message="hello world" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"

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

set -- /path/to/executable --option --message="hello world" --
set -- "$@" "$file1" "$file2"
"$@"

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

code='/path/to/executable --option --message="hello world" -- /path/to/file1 | grep "interesting stuff"'
eval "$code"

Обратите внимание на вложенные кавычки в определении code: одинарные кавычки '…'отделяют строковый литерал, так что значением переменной codeявляется строка /path/to/executable --option --message="hello world" -- /path/to/file1. evalВстроенный говорит оболочку , чтобы разобрать строку , переданную в качестве аргумента , как если бы он появился в сценарии, так что в этот момент котировка и трубы разобраны и т.д.

Использование evalсложно. Подумайте внимательно о том, что когда анализируется. В частности, вы не можете просто вставить имя файла в код: вам нужно заключить его в кавычки, как если бы оно было в файле исходного кода. Там нет прямого способа сделать это. Что - то вроде code="$code $filename"перерывов , если имя файла содержит какой - либо оболочки специальных символов (пробелы, $, ;, |, <, >и т.д.). code="$code \"$filename\""все еще ломается "$\`. Даже code="$code '$filename'"ломается, если имя файла содержит '. Есть два решения.

  • Добавьте слой кавычек вокруг имени файла. Самый простой способ сделать это - добавить одинарные кавычки вокруг него и заменить одинарные кавычки на '\''.

    quoted_filename=$(printf %s. "$filename" | sed "s/'/'\\\\''/g")
    code="$code '${quoted_filename%.}'"
  • Сохраняйте расширение переменной внутри кода, чтобы оно просматривалось при оценке кода, а не при построении фрагмента кода. Это проще, но работает, только если переменная все еще присутствует с тем же значением во время выполнения кода, а не, например, если код встроен в цикл.

    code="$code \"\$filename\""

Наконец, вам действительно нужна переменная, содержащая код? Самый естественный способ дать имя блоку кода - это определить функцию.

Что с тобой read?

Без -r, readпозволяет продолжить строки - это одна логическая строка ввода:

hello \
world

readразбивает строку ввода на поля, разделенные символами в $IFS(без -r, обратный слеш также экранирует их). Например, если вход представляет собой строку, содержащую три слова, тогда read first second thirdустанавливается firstпервое слово ввода, secondвторое слово и thirdтретье слово. Если есть больше слов, последняя переменная содержит все, что осталось после установки предыдущих. Ведущие и конечные пробелы обрезаются.

Установка IFSна пустую строку позволяет избежать обрезки. Посмотрите, почему `while IFS = read` используется так часто, вместо` IFS =; пока читаешь? для более длинного объяснения.

Что не так с xargs?

Формат ввода xargs- строки, разделенные пробелами, которые могут быть заключены в одинарные или двойные кавычки. Ни один стандартный инструмент не выводит этот формат.

Ввод в xargs -L1или xargs -lпочти список строк, но не совсем - если в конце строки есть пробел, следующая строка является продолжением.

Вы можете использовать xargs -0там, где это применимо (и где доступно: GNU (Linux, Cygwin), BusyBox, BSD, OSX, но его нет в POSIX). Это безопасно, потому что нулевые байты не могут появляться в большинстве данных, в частности в именах файлов. Чтобы создать разделенный нулями список имен файлов, используйте find … -print0(или вы можете использовать, find … -exec …как описано ниже).

Как мне обработать найденные файлы find?

find  -exec some_command a_parameter another_parameter {} +

some_commandдолжна быть внешней командой, это не может быть функция оболочки или псевдоним. Если вам нужно вызвать оболочку для обработки файлов, вызывайте shявно.

find  -exec sh -c '
  for x do
    … # process the file "$x"
  done
' find-sh {} +

У меня есть другой вопрос

Просмотрите тег на этом сайте, или или . (Нажмите «узнать больше…», чтобы увидеть общие советы и отобранный список общих вопросов.) Если вы искали и не нашли ответа, задайте вопрос .

жилль
источник
6
@ John1024 Это только функция GNU, поэтому я остановлюсь на «нет стандартного инструмента».
Жиль
2
Вам также нужны кавычки $(( ... ))(также $[...]в некоторых оболочках), за исключением zsh(даже в эмуляции sh) и mksh.
Стефан Шазелас
3
Обратите внимание, что xargs -0это не POSIX. За исключением FreeBSD xargs, вы обычно хотите xargs -r0вместо xargs -0.
Стефан Шазелас
2
@ John1024, нет, ls --quoting-style=shell-alwaysне совместим с xargs. Попробуйтеtouch $'a\nb'; ls --quoting-style=shell-always | xargs
Стефан Шазелас
3
Еще одна приятная (только для GNU) функция xargs -d "\n"позволяет запускать, например, locate PATTERN1 |xargs -d "\n" grep PATTERN2поиск имен файлов, соответствующих PATTERN1, и содержимого, соответствующего PATTERN2 . Без GNU вы можете сделать это, например, какlocate PATTERN1 |perl -pne 's/\n/\0/' |xargs -0 grep PATTERN1
Адам Кац
26

В то время как ответ Жиля превосходен, я беру вопрос в его главном

Всегда используйте двойные кавычки вокруг подстановок переменных и подстановок команд: "$ foo", "$ (foo)"

Когда вы начинаете с Bash-подобной оболочки, которая выполняет разбиение слов, да, конечно, безопасный совет - всегда использовать кавычки. Однако разделение слов не всегда выполняется

§ Разделение слов

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

foo=$bar
bar=$(a command)
logfile=$logdir/foo-$(date +%Y%m%d)
PATH=/usr/local/bin:$PATH ./myscript
case $foo in bar) echo bar ;; baz) echo baz ;; esac

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

Стивен Пенни
источник
19
Как я упоминаю в своем ответе, см. Подробности в unix.stackexchange.com/questions/68694/… . Обратите внимание на вопрос: «Почему мой сценарий оболочки задыхается?». Самая распространенная проблема (из многолетнего опыта работы на этом сайте и в других местах) - отсутствие двойных кавычек. «Всегда использовать двойные кавычки» легче запомнить, чем «всегда использовать двойные кавычки, за исключением тех случаев, когда они не нужны».
Жиль
14
Правила трудно понять для начинающих. Например, foo=$barэто нормально, но export foo=$barили env foo=$varнет (по крайней мере, в некоторых оболочках). Совет для начинающих: всегда указывайте свои переменные, если вы не знаете, что делаете, и у вас есть веская причина не делать этого .
Стефан Шазелас
5
@ StevenPenny Это действительно более правильно? Есть ли разумные случаи, когда кавычки сломали бы сценарий? В ситуациях , когда в половине случаях котировка должна быть использована, а в другой половине котировок может быть использована при необходимости - то рекомендация «всегда использовать кавычки, только в случае , если» это тот , который следует думать, так как это правда, простые и менее рискованные. Общеизвестно, что обучение таких списков исключений для начинающих неэффективно (без контекста, они их не запомнят) и контрпродуктивно, так как они будут путать нужные / ненужные цитаты, ломать свои сценарии и демотивировать их для дальнейшего обучения.
Петерис
6
Мои 0,02 доллара - это то, что я рекомендую цитировать все - хороший совет. Ошибочно цитировать то, что не нужно, безвредно, ошибочно не цитировать то, что действительно нужно, вредно. Таким образом, для большинства авторов сценариев оболочки, которые никогда не поймут хитросплетения того, когда именно происходит разделение слов, цитировать все гораздо безопаснее, чем пытаться цитировать только там, где это необходимо.
Godlygeek
5
@Peteris и godlygeek: «Есть ли разумные случаи, когда цитаты нарушали бы сценарий?» Это зависит от вашего определения «разумный». Если сценарий установлен criteria="-type f", то find . $criteriaработает, но find . "$criteria"не работает.
G-Man
22

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

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

Оболочка в своем сердце и душе (для тех, у кого она есть) является интерпретатором команд - это синтаксический анализатор, похожий на большой, интерактивный 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расширение оболочки с разделителями. По умолчанию задано значение <space><tab><newline>- все три из которых проявляют особые свойства, когда содержатся внутри $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

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

set +f

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

echo "*" *

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

set -f; echo "*" *

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

mikeserv
источник
Я на самом деле согласен с @ StéphaneChazelas, что это (в основном) путает вещи больше, чем помогает ... но я нашел это полезным, лично, поэтому я проголосовал. Теперь у меня есть лучшее представление (и несколько примеров) о том, как на IFSсамом деле работает. То , что я не получаю , почему это будет когда - либо быть хорошей идеей , чтобы установить IFSнечто иное , чем по умолчанию.
Wildcard
1
@Wildcard - это разделитель полей. если у вас есть значение в переменной, которое вы хотите расширить до нескольких полей, вы разделяете его $IFS. cd /usr/bin; set -f; IFS=/; for path_component in $PWD; do echo $path_component; doneпечатает \nпотом usr\nпотом bin\n. Первый echoпустой, потому что /это нулевое поле. Компоненты path_components могут иметь символы новой строки или пробелы или что-то еще - не имеет значения, потому что компоненты были разделены, /а не по умолчанию люди делают это awkвсе время, в любом случае. ваша оболочка тоже это делает
mikeserv
3

У меня был большой видео проект с пробелами в именах файлов и пробелами в именах каталогов. Хотя он find -type f -print0 | xargs -0работает для нескольких целей и в разных оболочках, я обнаружил, что использование пользовательского IFS (разделителя полей ввода) дает вам большую гибкость, если вы используете bash. Фрагмент ниже использует bash и устанавливает IFS просто как новую строку; при условии, что в ваших именах файлов нет новых строк:

(IFS=$'\n'; for i in $(find -type f -print) ; do
    echo ">>>$i<<<"
done)

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

Более того, установка IFS на новую строку позволяет заранее установить переменные оболочки и легко их распечатать. Например, я могу постепенно увеличивать переменную V, используя символы новой строки в качестве разделителей:

V=""
V="./Ralphie's Camcorder/STREAM/00123.MTS,04:58,05:52,-vf yadif"
V="$V"$'\n'"./Ralphie's Camcorder/STREAM/00111.MTS,00:00,59:59,-vf yadif"
V="$V"$'\n'"next item goes here..."

и соответственно:

(IFS=$'\n'; for v in $V ; do
    echo ">>>$v<<<"
done)

Теперь я могу «перечислить» настройку V с echo "$V"помощью двойных кавычек для вывода новых строк. (Благодарю эту ветку за $'\n'объяснение.)

Russ
источник
3
Но тогда у вас все еще будут проблемы с именами файлов, содержащими символы новой строки или глобуса. См. Также: Почему циклическая обработка выходных данных find является плохой практикой? , Если zshвы используете , вы можете использовать IFS=$'\0'и использовать -print0( zshне используйте глобализацию для расширений, поэтому символы глобуса здесь не проблема).
Стефан Шазелас
1
Это работает с именами файлов, содержащими пробелы, но не работает с потенциально враждебными именами файлов или случайными «бессмысленными» именами файлов. Вы можете легко решить проблему имен файлов, содержащих подстановочные знаки, добавив set -f. С другой стороны, ваш подход принципиально не работает с именами файлов, содержащими переводы строк. При работе с данными, отличными от имен файлов, также происходит сбой с пустыми элементами.
Жиль
Да, моя оговорка в том, что он не будет работать с символами новой строки в именах файлов. Тем не менее, я считаю, что мы должны провести черту, просто стесняясь безумия ;-)
Расс
И я не уверен, почему это получил отрицательный голос. Это совершенно разумный метод для перебора имен файлов с пробелами. Использование -print0 требует xargs, и есть вещи, которые трудно использовать в этой цепочке. Мне жаль, что кто-то не согласен с моим ответом, но это не повод, чтобы понизить его.
Расс
0

Принимая во внимание все вышеупомянутые последствия для безопасности и предполагая, что вы доверяете и контролируете переменные, которые вы расширяете, можно использовать несколько путей с использованием пробелов 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
Маттиас Вадман
источник