Скрипт для рекурсивной замены недопустимых символов в именах файлов, а не каталогов, с переименованием

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

bashделает несколько раскрытий фигурных скобок слева направо, поэтому

xargs -I_ cat _/{11..15}/{8..5}.jpg

расширяется до

xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg

, но мне нужно было сначала выполнить расширение второй скобки, что дало

xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg

Лучшее, что я мог придумать,

xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)

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

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

2
22.01.2020, 19:15
2 ответа

Почему ваш код не работает

Шаблон подстановочных знаков *.aviрасширяется оболочкой, которая запускает findперед запуском find, поэтому его действие зависит от того, есть ли файлы *.aviв текущем каталоге или нет. См. find not recursive when file at top для получения дополнительных пояснений. Чтобы расширить *.aviв подкаталогах,вам нужно будет сделать три вещи по-разному :указать шаблон, чтобы исходная оболочка не расширяла его; организовать запуск дополнительной оболочки в каждом подкаталоге для выполнения подстановочного расширения; и искать каталоги только с помощью команды find, а не любого типа файла.

Кроме того, ваш код вызывает renameдля каждого файла на любом уровне в текущем каталоге, включая сами подкаталоги, через {} +. Итак, renameработает с каталогами, а не только с обычными файлами.

Кроме того, в вашем коде Perl есть синтаксическая ошибка.

Рабочее решение с zsh

autoload -Uz zmv # best in ~/.zshrc

zmv -n '(**/)(*.avi)(#qD^/)' '$1${2//[^a-zA-Z0-9._-]/_}'

^/— выбрать любой тип файла, кроме каталога . Замените на .только для обычных файлов . -nдля работы всухую -. Удаляйте, когда счастливы.

Рабочее решение с findиrename

С вариантами renameна основе perl -и реализацией find, поддерживающей-execdir:

LC_ALL=C find. -depth -name '*[!a-zA-Z0-9._-]*.avi' ! -type d -execdir \
  rename 's/[^a-zA-Z0-9._-]/_/g' {} +

При таком подходе есть несколько предостережений:

  • Это запускает как минимум один renameэкземпляр на каталог, содержащий файлы для переименования (один renameна файл с некоторыми findреализациями/версиями, где -execdir... {} +фактически совпадает с -execdir... {} \;.(zmvзапускает один mvдля каждого файла, но вы можете сделать mvвстроенным с помощью zmodload zsh/files, чтобы ускорить его ).
  • С помощью -execdirfindзапускает команду в каталоге , который содержит эти файлы, и передает команде путь относительно этого каталога. Некоторые findреализации (GNU )добавляют префикс ./к файлам, некоторые — нет. Некоторые варианты renameпринимают опции после выражения Perl, что означает, что если у вас есть файл, имя которого начинается с -, это может вызвать проблемы.
  • мы должны использовать LC_ALL=Cдля -nameдля работы, даже если имена файлов содержат последовательности байтов, которые в противном случае не сформировали бы допустимые символы в локали. renameнаследует это и в любом случае в большинстве вариантов работает только с ASCII. Однако это означает, что он заменит многобайтовые символы -на столько _, сколько байтов содержит символ. Например, он переименует UTF -8 stéphaneв st__phaneвместо st_phane. zshподходит, потому что он преобразует как многобайтовые символы -, так и все байты, которые не могут быть декодированы в символы, в один _символ каждый.
  • в отличие от zsh's zmv, он не будет выполнять проверки работоспособности (например, 2 файла не будут иметь одинаковое имя, например a+b.aviиa@b.avi)до начала переименования. Однако renameне должен перезаписывать существующие файлы.
3
27.01.2020, 21:55

Если предположить, что

rename 's/[^A-Za-z0-9.-]/_/g' -- *.avi

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

find. -type d -exec sh -c '
    for dirpath do
        ( cd "$dirpath" && rename "s/[^A-Za-z0-9.-]/_/g" -- *.avi )
    done' sh {} +

Для пакетов найденных каталогов будет выполнен короткий скрипт в строке -, который переходит в найденный каталог и выполняет команду rename.

2
27.01.2020, 21:55

Теги

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