Завжди використовуйте подвійні лапки підстановок змінних і команд замін: "$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 {} +
У мене є ще одне питання
Перегляньте тег цитування на цьому веб-сайті або оболонку або оболонку-скрипт . (Клацніть на "дізнатися більше ...", щоб побачити деякі загальні поради та вибраний вручну список поширених питань.) Якщо ви шукали і не можете знайти відповідь, запитайте .
shellcheckдопоможуть вам покращити якість своїх програм.