Find and rename all files linux

How to rename multiple files using find

Why are you using basename ? Do you want ./2017/photos/holiday/dscn1234.jpg to be renamed to ./dscn1234.jpg_renamed in the top level directory?

8 Answers 8

The following is a direct fix of your approach:

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

However, this is very expensive if you have lots of matching files, because you start a fresh shell (that executes a mv ) for each match. And if you have funny characters in any file name, this will explode. A more efficient and secure approach is this:

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

It also has the benefit of working with strangely named files. If find supports it, this can be reduced to

find . -type f -name 'file*' -exec mv <> <>_renamed \; 

The xargs version is useful when not using <> , as in

find . -print0 | xargs --null rm 

Here rm gets called once (or with lots of files several times), but not for every file.

I removed the basename in you question, because it is probably wrong: you would move foo/bar/file8 to file8_renamed , not foo/bar/file8_renamed .

Edits (as suggested in comments):

In the case the x is useless: find . -type f -name ‘file*’ -exec mv <> «<>_renamed» \; xargs version have the same efficiency like the first example/

(1) It is very dangerous to use <> directly in a shell ( sh -c «…» ) command — you should always pass it in as an argument. (2) Not all versions of find support the <>_renamed construct. (3) I don’t understand your statement that xargs is useful for removing files (in contrast to renaming them).

@G-Man: The difference with xargs is not mv vs. rm , but use of <> vs. without. The former is similar to mv file1 file1_renamed; mv file2 file2_renamed while the latter is rm file1 file2 .

and if you then wanted to change the _renamed files back to their original names, how would you do that?

After trying the first answer and toying with it a little I found that it can be done slightly shorter and less complex using -execdir :

find . -type f -name 'file*' -execdir mv <> <>_renamed ';' 

Looks like it should also do exactly what you need.

With find implementations that support -execdir and <> not as a whole, it’s also the safest. You may want to add a -i to mv though (and -T if your mv supports it)

@StéphaneChazelas even better, instead of relying on mv for a prompt, or in addition to it, you can (no doubt depending on whether your implementation of find supports it) also use -okdir which will output the command to be executed before executing it.

Читайте также:  Аналог терминальный сервер linux

Argh, just found that -execdir does have one very annoying downside, find refuses to do anything if PATH contains any relative paths. askubuntu.com/questions/621132/… find: The relative path XXX is included in the PATH environment

$ find ./packages -type f -name ‘*.ddeb’ -execdir mv <> <>.deb + —> find: Only one instance of <> is supported with -execdir . + ; find —version —> find (GNU findutils) 4.7.0 . info «(find) Multiple Files» says: Only one ‘<>‘ is allowed within the command, and it must appear at the end, immediately before the ‘+’.

Another approach is to use a while read loop over find output. This allows access to each file name as a variable that can be manipulated without having to worry about additional cost / potential security issues of spawning a separate sh -c process using find ‘s -exec option.

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

And if the shell being used supports the -d option to specify a read delimiter you can support strangely named files (e.g. with a newline) using the following:

find . -type f -name 'file*' -print0 | while IFS= read -d '' file_name; do mv "$file_name" "$_renamed" done 

I had to search for files with specific prefix and drop them, this helped me a lot find . -name «prefix*» -maxdepth 1 | while IFS= read file_name ; do; mv $file_name «$«; done

I want to expand on the first answer and note that this won’t work to append to the filename since the ./ path prefix is present in the filename argument.

Modifying Thomas Erker answer, I find this one a more generic approach

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

—null Indicates that each argument passed through stdin ends with a null character ( \0 ). This way the filename can contain spaces, otherwise each word will be threated as a different parameter for the mv command.

-I replace-str Every ocurrence of replace-str will be replaced with the argument read from stdin . So, you may change it for other string if you need it so.

Источник

Переименование файлов с помощью find, sed и xargs

Данное сообщение не претендует на оригинальность, это просто очередной пример того, как можно переименовать много файлов с помощью командной строки в Linux, используя find , sed , xargs и mv .

Общий вид #

  1. Вывести имена нужных файлов ( ls , echo или find ).
  2. Произвести замену каждого имени и вывести старое имя и новое имя ( sed ).
  3. Применить к каждой паре старое имя – новое имя команду mv ( xargs ).

На языке bash эти 3 команды могут выглядеть так:

ls | sed | xargs mv echo | sed | xargs mv find | sed | xargs mv

Мы будем говорить о последнем варианте, поскольку find является очень мощной и гибкой утилитой для поиска файлов.

Читайте также:  Как открыть port linux

Пример №1 #

Простейший случай, необходимо произвести такую замену с большим количеством файлов:

01-filename.txt → 01-new.txt 02-filename.txt → 02-new.txt . 

Для экспериментов создадим директорию ~/test/ .

Теперь в директории ~/test/ должно появиться 5 пронумерованных файлов. Выведем эти файлы (точка после find означает, что поиск надо проводить в текущей директории; ключ -type f просит выводить только файлы).

find #

./1-filename.txt ./2-filename.txt ./5-filename.txt ./4-filename.txt ./3-filename.txt 

Потоковый редактор sed в команде find | sed получает поток названий файлов от find. И с каждым названием файла он может делать какую-то операцию. Нас интересует замена, общий вид которой в sed выглядит так.

sed #

Общий вид команд в редакторе sed:

sed 's/old/new/g' sed 's#old#new#g' sed 's:old:new:g' 

Если выполнить данные команды, то sed будет ждать ввода аргументов с клавиатуры. После каждого нажатия Enter sed выведет строку, заменив в ней ‘old’ на ‘new’, если ‘old’ будет присутствовать в введённой строке. Но обычно sed используется либо в связках типа find | sed , либо принимает в качестве аргумента имена файлов, чтобы произвести замены внутри файлов.

Заметьте, что вы сами можете выбирать разделитель — например, / , # или : (хотя можно использовать почти любой символ), комбинировать их нельзя. Буква ‘s’ — заменить (от англ. substitute); буква ‘g’ (от англ. global) в конце стоит для того, чтобы замена происходила во всей строке не только для первого вхождения, а столько раз, сколько там встретится «old». Когда в заменяемых выражениях встречаются слэши ( / или \ ), то каждую из них приходится предварять дополнительным обратным слэшем, тогда код слишком сильно напоминает частокол и удобнее использовать двоеточие или что-то другое в качестве разделителя.

Итак, посмотрим, что мы можем сделать двумя первыми шагами find | sed :

find . -type f | sed 's:filename:new:g'
./1-new.txt ./2-new.txt ./5-new.txt ./4-new.txt ./3-new.txt 

Отлично, нужные нам имена выведены. Но в третьем шаге утилите xargs нужны и старое, и новое имя файла, чтобы применить к ним команду mv . Поэтому модифицируем команду sed :

Знак «p;» (p — print), стоящий перед «s», просит sed выдавать не только результат, но и исходный материал. Теперь вывод будет такой:

find . -type f | sed 'p;s:filename:new:g'
./1-filename.txt ./1-new.txt ./2-filename.txt ./2-new.txt ./5-filename.txt ./5-new.txt ./4-filename.txt ./4-new.txt ./3-filename.txt ./3-new.txt 

То, что нужно. Переходим к третьему шагу.

xargs #

Программа xargs работает с потоками данных. Утилита принимает один поток и может «распараллелить» его на несколько. Чтобы было лучше понятно, посмотрите простые примеры:

echo "1 2 3 4 5 6 7 8 9" | xargs -n1
echo "1 2 3 4 5 6 7 8 9" | xargs -n2
echo "1 2 3 4 5 6 7 8 9" | xargs -n3
echo "1 2 3 4 5 6 7 8 9" | xargs -n4

Для переименования файлов, очевидно, нам нужен ключик -n2 , тогда xargs разделит поток из старых и новых имён в две колонки.

find . -type f | sed 'p;s:filename:new:' | xargs -n2
./1-filename.txt ./1-new.txt ./2-filename.txt ./2-new.txt ./5-filename.txt ./5-new.txt ./4-filename.txt ./4-new.txt ./3-filename.txt ./3-new.txt 

Отлично. Осталось перед каждой парой вставить mv , и переименование произойдёт. Выполняем:

find . -type f | sed 'p;s:filename:new:' | xargs -n2 mv

Вывода никакого не последует, но переименование произошло. Чтобы быть уверенным в результате, можно произвести проверку перед выполнением этой команды. Для этого надо добавить ключ -p к xargs . Тогда утилита будет показывать, что она сделала бы без ключа -p . Можно нажимать Enter и просматривать, что собирается сделать команда. Изменения применены не будут.

find . -type f | sed 'p;s:filename:new:' | xargs -n2 -p mv

Если результаты устраивают, то нужно убрать ключ -p и повторить команду.

Читайте также:  Command to connect to ftp in linux

Пример №2 #

В директориях dir1/subdir1/ , dir1/subdir2/ ,… dir5/subdir5/ лежат файлы filename.txt. Нужно вытащить их оттуда, но уже с разными именами.

dir1/subdir1/filename.txt → dir1-subdir1-filename.txt dir1/subdir2/filename.txt → dir1-subdir2-filename.txt . dir5/subdir5/filename.txt → dir5-subdir5-filename.txt 

При необходимости создадим и очистим нашу экспериментальную директорию ~/test/ и создадим там необходимую структуру файлов:

mkdir -p ~/test/ rm ~/test/* cd ~/test/
mkdir -p dir/subdir touch dir/subdir/filename.txt

Итак, у нас получилось 25 файлов filename.txt в директориях dir1/subdir1/ , dir1/subdir2 ,… dir5/subdir5/ .

find #

Выведем эти файлы. В этом случае -type f тоже сработал бы, но воспользуемся для разнообразия поиском по имени. Также используем звёздочку вместо точки, чтобы избавиться от ведущего ./ в выводе.

dir1/subdir2/filename.txt dir1/subdir4/filename.txt dir1/subdir1/filename.txt dir1/subdir5/filename.txt dir1/subdir3/filename.txt . 

sed #

Попросим sed заменить все слэши на дефисы.

find * -name 'filename.txt' | sed 'p;s:/:-:g'

xargs #

find * -name 'filename.txt' | sed 'p;s:/:-:g' | xargs -n2 -p mv

Убедившись, что всё произойдёт правильно, убираем ключ -p и повторяем команду.

Пример №3 #

В директории лежат файлы, пронумерованные от 0 до 1001. Если их выводить командой ls , они будут отсортированы по имени не так, как этого хотелось бы [1] . Добавим необходимое количество нулей в названия файлов.

0-filename.txt → 0000-filename.txt . 50-filename.txt → 0050-filename.txt . 150-filename.txt → 0150-filename.txt . 1001-filename.txt → 1001-filename.txt 

При необходимости создаём тестовую директорию и/или чистим её и создаём наши 1001 файл:

mkdir -p ~/test/ rm -r ~/test/* cd ~/test/

Можно действовать так (не лучший вариант).

user test $
user test $
user test $

ls | sed 'p;s:^\(5\)-:000\1-:g' | xargs -n2 mv ls | sed 'p;s:^\(49\)-:00\1-:g' | xargs -n2 mv ls | sed 'p;s:^\(835\)-:0\1-:g' | xargs -n2 mv

Первая команда всем файлам, начинающимся с одной цифры, добавит 3 нуля. Вторая команда всем файлам, начинающимся с двух цифр, добавит 2 нуля. Третья команда всем файлам, начинающимся с трёх цифр, добавит 1 ноль.

Здесь использованы регулярные выражения в sed . Разберём конструкцию

  • ^ означает, что нужно искать с начала имени
  • конструкция \(. \) позволяет заключённое в неё использовать как \1 в шаблоне замены
  • 148 означает три любые цифры подряд.
  • — — просто дефис.
  1. В принципе, с этим можно справиться командой ls -v (natural sorting: 10 будет выведено после 9, а не после единицы), но мы всё-таки для упражнения произведём переименование. ↩︎

© Сергей Лисаков, 2023. Сайт собран Hexo.

Источник

Оцените статью
Adblock
detector