Как изменить имена файлов без типа в bash

#bash

#bash

Вопрос:

Я пытаюсь переименовать кучу файлов, заменив определенную строку с помощью скрипта bash find на and rename . Это работает для файлов, когда я указываю тип файла, но у меня есть несколько файлов, которые мне нужно изменить, у которых нет типа файла, и я не могу понять, как их переименовать.

Для файлов с типом файла я успешно использую это, чтобы заменить $old_session на $new_session, например, в каждом файле .jpg:

 old_session='123'
new_session='456'
find my_folder -name '*.jpg' -type f -exec rename $old_session $new_session *.jpg {} ;
  

Но когда я пытаюсь сделать то же самое для файлов, у которых нет типа файла, ничего не происходит:

 find my_folder -name '*' -type f -exec rename $old_session $new_session * {} ;
  

или

 find my_folder -name '*${old_session}*' -type f -exec rename $old_session $new_session *${old_session}* {} ;
  

Как я могу изменить имя файла файлов без указанного типа?

Спасибо!

Ответ №1:

Это работает для шаблона *.jpg , потому jpg что в папке, в которой вы работаете, нет файлов find , и оболочка передает *.jpg unexpanded find команде, которая передает ее unexpanded rename , и это работает.

Но когда вы делаете:

 find my_folder -name '*' -type f -exec rename $old_session $new_session * {} ;
  

расширяется * со всеми файлами из текущего каталога и передается rename как есть, а это не то, что вы хотите.

Сделайте это вместо:

 find my_folder -name '*' -type f -exec rename $old_session $new_session "*" {} ;
  

(это также лучше для .jpg файлов, кстати :))

Комментарии:

1. Это работает, но не изменяет файлы, в которых $old_session находится в имени файла дважды. Есть ли способ сделать это в той же строке, или мне нужно запустить его во второй раз?

2. rename man говорит: «rename переименует указанные файлы, заменив первое вхождение выражения в их имени заменой.». Так что это бесполезно. Но вы могли бы создать multirename скрипт, который принимал бы те же аргументы и делал бы то, что вы хотите.

3. Я предупреждаю вас, что создание multirename скрипта не является тривиальным, с именами файлов с пробелами и т.д… так что, если вы хотите пойти по этому пути, подготовьтесь к другому вопросу о stackoverflow 🙂 запустить его дважды было бы проще для редких случаев, с которыми вы столкнетесь.

4. Если вы пытаетесь продемонстрировать здесь хорошие практики, требуется некоторое дополнительное цитирование. $old_session не гарантируется, что они будут идентичны "$old_session" .

Ответ №2:

Я бы сделал это так:

Допустим, наш «old_session» — kkk, а наш «new_session» — «noname»

Рассмотрим следующие файлы в каталоге. Пара файлов kkk и .sh.

 bash$> ls -lrt
total 0
-rw-r--r--. 1 root root 0 Oct  7 03:03 3432kkk
-rw-r--r--. 1 root root 0 Oct  7 03:03 334kkk
-rw-r--r--. 1 root root 0 Oct  7 03:03 222kkk
-rw-r--r--. 1 root root 0 Oct  7 03:03 190kkk
-rw-r--r--. 1 root root 0 Oct  7 03:03 123kkk
-rw-r--r--. 1 root root 0 Oct  7 03:03 c.sh
-rw-r--r--. 1 root root 0 Oct  7 03:03 b.sh
-rw-r--r--. 1 root root 0 Oct  7 03:03 a.sh
  

Используемая команда / скрипт выполнит следующие шаги:

  1. Он найдет все файлы в каталоге.
  2. удалите ./ из их передней части (в результате выполнения команды find).
  3. выделите любое имя файла, содержащее точку, что означает расширение (например: c.sh имеет точку, поэтому она будет отфильтрована).
  4. прочитайте список имен файлов, один за другим, подготовьте его новое имя с помощью команды (new= echo $file|sed "s/$old_session//g" ).
  5. переименование выполняется с помощью команды mv.
  6. распечатайте результат операции.
  7. отобразите вывод ls -lrth после обработки для проверки.

Вот команда / скрипт и его вывод.

 bash$> find . -type f | sed 's/^.///g' | grep -v "." | while read file
> do
> new=`echo $file|sed "s/$old_session//g"`
> mv $file ${new}_${new_session}
> echo "$file renamed to ${new}_${new_session}"
> done
ls -lrt
123kkk renamed to 123_noname
222kkk renamed to 222_noname
334kkk renamed to 334_noname
3432kkk renamed to 3432_noname
190kkk renamed to 190_noname
bash$> ls -lrt
total 0
-rw-r--r--. 1 root root 0 Oct  7 03:03 c.sh
-rw-r--r--. 1 root root 0 Oct  7 03:03 b.sh
-rw-r--r--. 1 root root 0 Oct  7 03:03 a.sh
-rw-r--r--. 1 root root 0 Oct  7 03:09 3432_noname
-rw-r--r--. 1 root root 0 Oct  7 03:09 334_noname
-rw-r--r--. 1 root root 0 Oct  7 03:09 222_noname
-rw-r--r--. 1 root root 0 Oct  7 03:09 123_noname
-rw-r--r--. 1 root root 0 Oct  7 03:09 190_noname
bash$>
  

Комментарии:

1. Этот код действительно довольно глючный. По крайней мере, исправить проблемы с цитированием, выявленные shellcheck.net , и переключитесь с echo $file на printf '%sn' "$file" — смотрите Спецификацию POSIX echo , чтобы оценить, насколько широк диапазон обстоятельств, при которых его выходные данные не определены.

2. было бы и эффективнее, и правильнее вместо этого писать new=${file//$old_session/} , поэтому не нужно echo ни sed того, ни другого. На этом этапе у вас все равно будут только ваши ошибки в цитировании.

3. …ну, не только ошибки цитирования. Не использование -r аргумента to read означает, что обратные косые черты в именах файлов будут автоматически удалены. А не очистка IFS read означает, что вы будете молча удалять конечные пробелы в именах файлов. А не использование строк с нулевыми разделителями означает, что имена файлов, содержащие буквенные новые строки, будут проблемой.

4. Вау.. Я всего этого не знал. Спасибо, что указали на это, я попробую их и обновлю свой ответ. Я не думал обо всех этих вещах, когда писал это, но вы заставили меня понять это сейчас.

5. re: подход к строкам с нулевыми разделителями, см. UsingFind для некоторых примеров — поиск -print0 ; вы также можете выполнить поиск того же аргумента в BashFAQ # 1 .