Как переименовать несколько файлов, используя find

37

Я хочу переименовать несколько файлов (file1 ... filen в file1_renamed ... filen_renamed), используя findкоманду:

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

Но получаю эту ошибку:

mv: cannot stat filename=./file1’: No such file or directory

Это не работает, потому что имя файла не интерпретируется как переменная оболочки.

user164014
источник

Ответы:

46

Следующее является прямым исправлением вашего подхода:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

Тем не менее, это очень дорого, если у вас много подходящих файлов, потому что вы запускаете новую оболочку (которая выполняет a mv) для каждого соответствия. И если у вас есть забавные символы в любом имени файла, это взорвется. Более эффективный и безопасный подход заключается в следующем:

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

Это также имеет преимущество работы со странно названными файлами. Если findподдерживает это, это может быть уменьшено до

find . -type f -name 'file*' -exec mv {} {}_renamed \;

xargsВерсия полезна , когда не используется {}, так как в

find .... -print0 | xargs --null rm

Здесь rmвызывается один раз (или с большим количеством файлов несколько раз), но не для каждого файла.

Я удалил basenameв вас вопрос, потому что это , вероятно , неправильно: вы бы перейти foo/bar/file8к file8_renamedне foo/bar/file8_renamed.

Редактирует (как предложено в комментариях):

  • Добавлено сокращено findбезxargs
  • Добавлена ​​наклейка безопасности
Томас Эркер
источник
В случае, если xэто бесполезно: find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargsверсия имеет ту же эффективность, что и в первом примере /
Costas
Это xтолько для того, чтобы напрямую исправить подход аскера.
Томас Эркер
2
(1) Очень опасно использовать {}непосредственно в sh -c "…"команде shell ( ) - вы всегда должны передавать ее в качестве аргумента. (2) Не все версии findподдерживают {}_renamedконструкцию. (3) Я не понимаю вашего утверждения, xargsкоторое полезно для удаления файлов (в отличие от их переименования).
G-Man говорит: «Восстановите Монику»
@ G-Man: разница с xargsне mvпротив rm, но использование {}против без. Первое похоже на mv file1 file1_renamed; mv file2 file2_renamedто, в то время как второе rm file1 file2.
Томас Эркер
1
и если бы вы захотели изменить файлы _renamed на их исходные имена, как бы вы это сделали?
mcmillab
17

Попробовав первый ответ и немного поиграв с ним, я обнаружил, что это можно сделать немного короче и менее сложно, используя -execdir:

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

Похоже, он должен делать именно то, что вам нужно.

Matijs
источник
2
С findреализациями, которые поддерживают, -execdirа {}не в целом, это также самый безопасный. Вы можете добавить -iк mvхотя (и -Tесли ваш mvподдерживает его)
Стефан Chazelas
1
@ StéphaneChazelas еще лучше, вместо того, чтобы полагаться на mvподсказку или в дополнение к ней, вы можете (без сомнения, в зависимости от того, findподдерживает ли ваша реализация это) и использовать -okdirкоманду, которая будет выводить команду, которая будет выполнена перед ее выполнением.
Матейс
Стоит отметить, что -depthэто также хорошая идея, если вы собираетесь также коснуться имен каталогов.
Сиро Сантилли 事件 改造 中心 法轮功 六四 事件
Argh, только что обнаружил, что -execdirимеет один очень неприятный недостаток, findотказывается делать что-либо, если PATHесть какие-либо относительные пути ... askubuntu.com/questions/621132/… find: The relative path XXX is included in the PATH environment
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
6

Другой подход заключается в использовании while readциклического findвывода. Это позволяет получить доступ к каждому имени файла в качестве переменного , которой можно манипулировать без необходимости беспокоиться о дополнительных затратах / потенциале вопросах безопасности порождая отдельный sh -cпроцесс , используя find«s -execварианта.

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

И если используемая оболочка поддерживает -dопцию для указания readразделителя, вы можете поддерживать файлы со странными именами (например, с новой строкой), используя следующее:

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done
Джон Б
источник
3

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

Изменяя ответ Томаса Эркера, я считаю, что этот подход более общий

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

Параметры XARGS:

--nullУказывает, что каждый переданный аргумент stdinзаканчивается нулевым символом ( \0). Таким образом, имя файла может содержать пробелы, в противном случае каждое слово будет рассматриваться как отдельный параметр для mvкоманды.

-I replace-strКаждое вхождение replace-strбудет заменено аргументом, прочитанным из stdin. Таким образом, вы можете изменить его на другую строку, если вам это нужно.

Sdlion
источник
Почему pringf "%f\0"вместо print0?
SLM
1
@sim, потому что -print0будет генерировать ./префиксы, если PATTERNсодержит метасимволы оболочки, которые мешают при переименовании чего-то, что префиксирует исходные имена. (например, переименовать 0 - foo.txtв 00 - foo.txt, 1 - bar.txtв 01 - bar.txtи т. д.)
das-g
2

Я был в состоянии сделать что - то подобное с for, findи mv.

for i in $(find . -name 'config.yml'); do mv $i $i.bak; done

Это находит все config.ymlфайлы и переименовывает их вconfig.yml.bak

Варун Рисбуд
источник
это имеет преимущество в возможности использовать переменное расширение, например. $ {я: г}. Хороший ответ.
Салями
Да, вы можете в значительной степени поместить любое выражение в forи выполнить массовую операцию.
Варун Рисбуд