среда, 18 июня 2008 г.

Массовое переименование файлов с помощью утилиты find

Иногда бывает жизненно необходимо переименовать кучу файлов. Например заменить пробелы в названии файлов и директорий на нижний знак подчеркивания.
Сохраняем приведенный ниже скрипт в файл с именем mass-replace.sh

#!/bin/sh

# Определяем глубину вложенности директорий
depth=$((`find "$1" -type d -print | \
sed -E 's/[^\/]+//g' | \
sort | tail -n 1 | \
wc -c`+1));
# Макрос, переименовывающий файлы и директории
replacer='new="`dirname "{}"`/`basename "{}" | sed "s/ /_/g"`"; if [ "$new" != "{}" ]; then mv "{}" "$new"; fi;';
# Обходим все уровни вложенности
while [ $depth -ge 0 ];
do
# Находим и заменяем
find "$1" -depth $depth -and -name '* *' -print -exec sh -c "$replacer" \;
# Меняем уровень вложенности
depth=$(($depth-1));
done;
Сделаем файл исполняемым
$ chmod +x mass-replace.sh
В качестве аргумента скрипту передается путь до обрабатываемой директории
$ ./mass-replace.sh ~/Warez/Music

17 комментариев:

vti комментирует...

А перловская приблуда rename?

find ... | xargs rename 's/ /_/g'

Князь комментирует...

ну попробуйте, мне тоже интересно :)

vti комментирует...

А чего пробовать? Я всегда так делаю :)

Князь комментирует...

Сами вложенные директории и их файлы тоже обрабатывает?

Князь комментирует...

$ find test | xargs rename 's/ /_/g'
rename: too many arguments.
rename: too many arguments.

Анонимный комментирует...

sh -c "$replacer" — это хорошо

Я обычно для exec маленький скриптик писал, временный… Такое простое решение как-то в голову не приходило. Спасибо.

Если пачку нужных REPLACERs теперь поместить куда-нибудь в .bashrc, то пользоваться find для массовых операций над файлами будет удобнее…

vti комментирует...

Да, вы правы, вложенные директории не обрабатываюся :(

Shortbread комментирует...

Несколько поздно, конечно, но мне ваш скрипт пригодился сегодня только. У меня вопрос такой - а зачем нужны выяснять глубину вложенности и потом идти по уровням? Ведь просто find тоже способен пройти по всем файлам?

Князь комментирует...

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

Князь комментирует...

Поправка: ...«и вследствие чего дочерние директории и файлы»...

Shortbread комментирует...

Понял, спасибо большое. Сам бы не догадался :(

Анонимный комментирует...

Всё проще гораздо:
http://linuxforum.ru/index.php?s=&showtopic=73512&view=findpost&p=716305
Один find и один цикл.

Князь комментирует...

С сортировкой действительно получается проще.
find -name "* *" | sort -r | while read i; do mv "$i" "$(dirname "$i")/$(basename "$i" | tr ' ' _)"; done

Анонимный комментирует...

Да, кстати: синтаксис `...` уже давно deprecated. Рекомендуется использовать $(...). Я даже как-то на ошибку напоролся: http://forum.posix.ru/viewtopic.php?id=943

Вадим комментирует...

rename всё же удобнее, да и команда всего одна нужна.

Вот описание на русском с примерами.

Князь комментирует...

rename НЕ умеет обрабатывать ВЛОЖЕННЫЕ директории.

Анонимный комментирует...

Но в комбинации с find умеет )

find . -exec rename 's/ /_/g' {} \; -print

Это как альтернатива тому что предложил vti.