Як перейменувати кілька файлів за допомогою find


37

Я хочу перейменувати кілька файлів (file1 ... filen to 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

Це не працює, оскільки ім'я файлу не інтерпретується як змінна оболонки.

Відповіді:


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) Використовувати команду {}shell ( sh -c "…") дуже небезпечно - ви завжди повинні передавати її як аргумент. (2) Не всі версії findпідтримують {}_renamedконструкцію. (3) Я не розумію вашої заяви, яка xargsкорисна для видалення файлів (на відміну від перейменування їх).
G-Man каже: "Відновіть Моніку"

@ G-Man: Різниця з xargsне mvпроти rm, а використання {}проти без. Перший схожий на mv file1 file1_renamed; mv file2 file2_renamedтой час, як другий rm file1 file2.
Thomas Erker

1
і якби ви тоді хотіли змінити _renamed файли назад на їх початкові назви, як би ви це зробили?
mcmillab

17

Спробувавши першу відповідь і трохи погравши з нею, я виявив, що це можна зробити трохи коротше і менш складне, використовуючи -execdir:

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

Схоже, він також повинен робити саме те, що потрібно.


2
З findреалізаціями, які підтримують, -execdirа {}не в цілому, це також найбезпечніше. Ви можете додати -iдо mvхоча (і -Tякщо ваші mvпідтримує)
Stéphane Chazelas

1
@ StéphaneChazelas ще краще, замість того, щоб покладатися на mvпідказку, або на додаток до неї, ви можете (без сумніву, залежно від того, findпідтримує чи ваша підтримка), також використовувати, -okdirяка виведе команду, яку потрібно виконати перед її виконанням.
Matijs

Варто згадати, що -depthце також гарна ідея, якщо ви також збираєтесь торкнутися імен каталогів.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Арг, щойно виявив, що -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. Отже, ви можете змінити його на інший рядок, якщо він вам потрібен.


Чому 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


це має перевагу в тому, що можна використовувати змінне розширення, наприклад. $ {i: r}. Гарна відповідь.
Саламі

Так, ви можете майже вкласти будь-який вираз у forта виконати об'ємну операцію.
Варун Рісбуд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.