Чому мій сценарій оболонки задавлюється пробілом або іншими спеціальними символами?


284

Або вступне керівництво з надійної обробки файлів і інших рядків, що передаються в скриптах оболонки.

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

У мене виникла така проблема, як:

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

Що відбувається і як це виправити?


16
shellcheckдопоможуть вам покращити якість своїх програм.
Aurelien

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


1
@bli Ні. Це означає, що лише помилки потребують більше часу. Сьогодні приховують помилок. А тепер ви не знаєте всіх імен файлів, які згодом використовувались із кодом.
Волкер Зігель

По-перше, якщо ваші параметри містять пробіли, то їх потрібно зазначати (в командному рядку). Однак ви можете захопити весь командний рядок і проаналізувати його самостійно. Два пробіли не повертаються в один простір; будь-яка кількість місця говорить вашому сценарію, що є наступною змінною, тому якщо ви робите щось на кшталт "echo $ 1 $ 2", це ваш сценарій, розміщуючи один пробіл між ними. Також використовуйте "find (-exec)" для ітерації файлів з пробілами, а не для циклу; ви можете легше впоратися з просторами.
Патрік Тейлор

Відповіді:


352

Завжди використовуйте подвійні лапки підстановок змінних і команд замін: "$foo","$(foo)"

Якщо ви використовуєте без $fooкотирування, ваш скрипт буде задихатися від вхідних даних або параметрів (або виведення команди, з $(foo)), що містять пробіл або \[*?.

Там ви можете перестати читати. Ну добре, ось ще кілька:

  • read- Щоб читати вхідні рядки за рядком із readвбудованим, використовуйте "while IFS= read -r line; do …
    Звичайна" 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 (наприклад, зола або тире) не має змінних масивів.

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'"перерви, якщо ім'я файлу містить a '. Є два рішення.

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

    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
Stéphane Chazelas

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 "*" *

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


Я фактично погоджуюся з @ 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'пояснення.)


3
Але тоді у вас все ще будуть проблеми з іменами файлів, що містять символи нового рядка або глобуса. Дивіться також: Чому зациклість на поганій практиці пошуку результатів пошуку . Якщо zshви користуєтесь , ви можете користуватися IFS=$'\0'та використовувати -print0( zshне робиться глобалізація на розширення, тому символи глобуса там не є проблемою).
Стефан Шазелас

1
Це працює з іменами файлів, що містять пробіли, але це не працює проти потенційно ворожих імен файлів або випадкових «безглуздих» імен файлів. Ви можете легко виправити проблему з іменами файлів, що містять символи підстановки, додавши їх set -f. З іншого боку, ваш підхід принципово не відповідає іменам файлів, що містять нові рядки. У роботі з іншими даними, крім імен файлів, вони також не входять із порожніми елементами.
Жиль

Правильно, мій застереження полягає в тому, що він не працюватиме з новими рядками у назви файлів. Однак, я вважаю, що ми повинні підкреслити межу, просто соромлячись божевілля ;-)
Russ

І я не впевнений, чому це отримало протизаконну заяву. Це цілком розумний метод ітерації файлів із пробілами. Використання -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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.