На вопрос Как переименовать все файлы в папке с именем, оканчивающимся на «_backup» @Radu Rădeanu дал хороший ответ, который также пригодился мне:
find . -type f -name '*.jpg_backup' -print0 \
| while IFS= read -r -d '' file ; do mv -- "$file" \
"$(echo $file | sed 's/_backup//g')"; done
Однако Я бы хотел полностью понять его однострочность. Точно, часть, которую я не понимаю:
while IFS= read -r -d '' file
Я понимаю, что IFS является «внутренним разделителем полей», и я предполагаю, что это удаляет или игнорирует пробелы, но я не понимаю синтаксис и варианты.
Я также хотел бы понять, почему --
необходимо после mv.
Может ли кто-нибудь помочь? Благодаря.
read word1 word2 … rest
выполняет следующее:
IFS
считается разделителем слов. word
, второе слово переменной word2
и т. Д. rest
, с сохранением оригинальных разделителей слов. Опция -r
деактивирует шаг 2, поэтому \
в конце строки не считается разделителем слов.
Если для IFS
задана пустая строка, разделителей слов нет, поэтому вся строка представляет собой одно большое слово: шаг 3 ничего не делает, а шаг 5 заканчивается назначением исходной строки указанной переменной. Относительно того, почему IFS
установлено в этом месте, см. Почему while IFS= read
используется так часто, вместо IFS=; while read..
? .
Опция -d
изменяет понятие строки: обычно строка заканчивается символом новой строки; с -d ''
(пустой аргумент для -d
), bash вместо этого читает входные записи, разделенные нулевым байтом.
В результате find … -print0 | while IFS= read -r -d '' file; do …
выполняет тело цикла для каждого совпадения, которое печатает find
, независимо от любых специальных символов, которые могут встречаться в имени файла.
Что касается --
в mv -- "$file"
, то оно есть в том случае, если значение file
начинается с тире: mv
интерпретирует его как опцию. В данном конкретном случае в этом нет необходимости, поскольку выход этой команды find
всегда начинается с ./
. Систематическое использование --
в сценариях, возможно, является хорошей гигиеной.
В этом фрагменте есть дефект: он по-прежнему не справляется с именами файлов, содержащими пробелы или \[?*
, из-за бита "$(echo $file | sed 's/_backup//g')"
. Расширение переменной, такое как $file
, не просто подставляет значение переменной: оно разбивает переменную на слова, используя значение IFS
(как это делает read
), и обрабатывает каждое слово как шаблон подстановочного знака, который заменяется списком подходящих файлов, если он совпадает с любым. Чтобы избежать такого поведения, напишите "$file"
. Это общее правило программирования оболочки: всегда ставьте двойные кавычки вокруг подстановок переменных (и подстановок команд $(…)
тоже), если только вы не знаете, почему их нужно опускать. (Если вам нужна мелочь, см. Когда необходимо заключать в двойные кавычки? ; $ VAR vs $ {VAR} и цитировать или не цитировать и . Что такое значимость одинарных и двойных кавычек в переменных среды? также может представлять интерес).
Быстрое исправление заключается в добавлении двойных кавычек:
mv -- "$file" "$(echo "$file" | sed 's/_backup//g')"
Это работает здесь из-за способа ввода данных, но для общих значений file
он терпит неудачу в нескольких редких случаях. случаи:
file
состоит из символа -
, за которым следуют одна или несколько букв среди Een
, то echo
будет рассматривать его как опцию и ничего не выводить. $file
заканчивается одним или несколькими символами новой строки, они будут усечены. Bash имеет строковую подстановку, которую можно использовать вместо sed здесь. Это более надежно (вам не нужно беспокоиться о тонкостях со специальными символами) и быстрее.
mv -- "$file" "${file//_backup/}"
Несмотря на то, что задан вопрос , это неправильная операция: она удаляет _backup
в любом месте имени файла, а не только в конце. Здесь сделать это правильно будет проще.
mv -- "$file" "${file%_backup}"
Если у вас есть имя файла, начинающееся с дефиса, вам нужно --
отделить опции mv от фактических аргументов имени файла.
Если у вас есть имя файла, которое начинается или оканчивается пробелами, то заклинание чтения с IFS=
обеспечит, чтобы переменная file
содержала именно то имя файла, которое вам нужно.
Опция -d ''
считывает разделенные нулем байты «строки» вместо строк, разделенных новой строкой - это потому, что вы используете опцию find -print0
.