Как рекурсивно изменить расширение нескольких файлов из командной строки?

73

У меня много файлов с .abcрасширением, и я хочу изменить их на .edefg
Как это сделать из командной строки?

У меня есть корневая папка со многими подпапками, поэтому решение должно работать рекурсивно.

tommyk
источник

Ответы:

85

Переносимый способ (который будет работать на любой POSIX-совместимой системе):

find /the/path -depth -name "*.abc" -exec sh -c 'mv "$1" "${1%.abc}.edefg"' _ {} \;

В bash4 вы можете использовать globstar для получения рекурсивных глобусов (**):

shopt -s globstar
for file in /the/path/**/*.abc; do
  mv "$file" "${file%.abc}.edefg"
done

Команда (perl) renameв Ubuntu может переименовывать файлы, используя синтаксис регулярных выражений perl, который вы можете комбинировать с globstar или find:

# Using globstar
shopt -s globstar
files=(/the/path/**/*.abc)  

# Best to process the files in chunks to avoid exceeding the maximum argument 
# length. 100 at a time is probably good enough. 
# See http://mywiki.wooledge.org/BashFAQ/095
for ((i = 0; i < ${#files[@]}; i += 100)); do
  rename 's/\.abc$/.edefg/' "${files[@]:i:100}"
done

# Using find:
find /the/path -depth -name "*.abc" -exec rename 's/\.abc$/.edefg/' {} +

Также см. Http://mywiki.wooledge.org/BashFAQ/030

geirha
источник
первый просто добавляет новое расширение к старому, оно не заменяет ...
user2757729
3
@ user2757729, он заменяет его. "${1%.abc}"расширяется до значения, $1кроме без .abcв конце. Смотрите faq 100 для более подробной информации о манипуляциях с оболочкой.
гейра
Я запустил это на файловой структуре ~ 70 тыс. Файлов ... по крайней мере, на моей оболочке это определенно не заменило ее.
user2757729
1
@ user2757729 Вы, вероятно, оставили "abc" в${1%.abc}...
kvnn
1
Если кому-то еще интересно, что подчеркивание делает в первой команде, это необходимо, потому что с sh -c первым аргументом назначается $ 0, а все остальные аргументы назначаются позиционным параметрам . Таким образом, _аргумент является фиктивным, а '{}' - первым позиционным аргументом для sh -c.
hellobenallan
48

Это сделает необходимую задачу, если все файлы находятся в одной папке

rename 's/.abc$/.edefg/' *.abc

Чтобы переименовать файлы рекурсивно, используйте это:

find /path/to/root/folder -type f -name '*.abc' -print0 | xargs -0 rename 's/.abc$/.edefg/'
чакра
источник
8
Или rename 's/.abc$/.edefg/' /path/to/root/folder/**/*.abcв современной версии Bash.
Адам Быртек
Большое спасибо Адаму за то, что он дал мне подсказку о том, как рекурсивно использовать * .abc в папках!
Rafał Cieślak
Отличный совет, спасибо! Где я могу найти больше документации о небольшом фрагменте синтаксиса регулярных выражений? Как то, что sв начале, и какие другие варианты я могу использовать там. Спасибо !
Anto
@AdamByrtek Я только что потерпел неудачу с твоим предложением об утопике. («Нет таких файлов» или что-то в этом роде.)
Джонатан Я.
14

Одна проблема с рекурсивными переименованиями состоит в том, что какой бы метод вы ни использовали для поиска файлов, он передает весь путь rename, а не только имя файла. Это затрудняет выполнение сложных переименований во вложенных папках.

Я использую find«s -execdirдействия , чтобы решить эту проблему. Если вы используете -execdirвместо -exec, указанная команда запускается из подкаталога, содержащего соответствующий файл. Таким образом, вместо того, чтобы пройти весь путь rename, он только проходит ./filename. Это значительно облегчает написание регулярного выражения.

find /the/path -type f \
               -name '*.abc' \
               -execdir rename 's/\.\/(.+)\.abc$/version1_$1.abc/' '{}' \;

В деталях:

  • -type f означает только искать файлы, а не каталоги
  • -name '*.abc' означает совпадение только с именами файлов, заканчивающимися на .abc
  • '{}'это заполнитель, который отмечает место, куда -execdirбудет вставлен найденный путь. Одинарные кавычки необходимы, чтобы позволить ему обрабатывать имена файлов с пробелами и символами оболочки.
  • Обратные косые черты после -typeи -nameявляются символом продолжения строки bash. Я использую их, чтобы сделать этот пример более читабельным, но они не нужны, если вы поместите свою команду в одну строку.
  • Однако обратная косая черта в конце -execdirстроки обязательна. Это здесь, чтобы избежать точки с запятой, которая завершает команду, которой управляет -execdir. Веселье!

Объяснение регулярного выражения:

  • s/ начало регулярного выражения
  • \.\/соответствует ведущему ./, который передается в -execdir. Используйте \ для выхода из. и / метасимволы (примечание: эта часть зависит от вашей версии find. См. комментарий пользователя @apollo)
  • (.+)соответствовать имени файла. Скобки фиксируют совпадение для последующего использования.
  • \.abc избежать точки, сопоставить азбуку
  • $ закрепить совпадение в конце строки

  • / отмечает конец части регулярного выражения "match" и начало части "replace"

  • version1_ добавить этот текст к каждому имени файла

  • $1ссылается на существующее имя файла, потому что мы захватили его в скобках. Если вы используете несколько наборов скобок в части «match», вы можете ссылаться на них здесь, используя $ 2, $ 3 и т. Д.
  • .abcимя нового файла будет заканчиваться на .abc. Нет необходимости экранировать метасимвол точки здесь, в разделе «заменить»
  • / конец регулярного выражения

До

tree --charset=ascii

|-- a_file.abc
|-- Another.abc
|-- Not_this.def
`-- dir1
    `-- nested_file.abc

После

tree --charset=ascii

|-- version1_a_file.abc
|-- version1_Another.abc
|-- Not_this.def
`-- dir1
    `-- version1_nested_file.abc

Подсказка: renameопция -n полезна. Он выполняет пробный прогон и показывает, какие имена он изменит, но не вносит никаких изменений.

Кристиан Лонг
источник
Спасибо за подробное объяснение, но, как ни странно, когда я пытаюсь это сделать, в --execdirначале не проходит, ./поэтому \.\/часть в регулярном выражении может быть опущена. Может это потому, что я на OSX, а не на Ubuntu
Аполлон
5

Еще один портативный способ:

find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;
aNeutrino
источник
4
Добро пожаловать в Спросите Ubuntu! Хотя это ценный ответ, я рекомендую расширить его (путем редактирования), чтобы объяснить, как и почему эта команда работает.
Элия ​​Каган
3
# Rename all *.txt to *.text
for f in *.txt; do 
mv -- "$f" "${f%.txt}.text"
done

Также смотрите запись о том, почему вы не должны анализировать ls.

Редактировать: если вам нужно использовать базовое имя, ваш синтаксис будет:

for f in *.txt; do
mv -- "$f" "$(basename "$f" .txt).text"
done

https://unix.stackexchange.com/questions/19654/changing-extension-to-multiple-files

ashanrupasinghe
источник
0

Это то, что я делал и работал довольно так, как я хотел. Я использовал mvкоманду. У меня было несколько .3gpфайлов, и я хотел переименовать их все.mp4

Вот короткий вкладыш для этого:

for i in *.3gp; do mv -- "$i" "ren-$i.mp4"; done

Который просто просматривает текущий каталог, выбирает все файлы .3gp, затем переименовывает (используя mv) в ren-name_of_file.mp4

KhoPhi
источник
Это переименует file.3gpв file.3gp.mp4, что может быть не то, что большинство людей хотят.
Муру
@muru, но принцип все еще существует. Просто выберите любое расширение, затем укажите расширение назначения, чтобы переименовать его. Здесь .3gpили .mp4только для иллюстрации.
Хофи
Синтаксис предпочтителен, однострочен и прост для понимания. Однако я бы использовал basename "$i" .mp4предыдущее расширение вместо «ren- $ i.mp4».
TaoPR
Правда. Хорошая точка зрения.
Хофи
0

Я нашел простой способ добиться этого. Чтобы изменить расширения многих файлов с jpg на pdf, используйте:

for file in /path/to; do mv $file $(basename -s jpg $file)pdf ; done
мирное
источник
0

Я бы использовал команду mmv из одноименного пакета :

mmv ';*.abc' '#1#2.edefg'

Эти ;матчи ноль или более , */и соответствует #1в замене. В *соответствует к #2. Не рекурсивная версия будет

mmv '*.abc' '#1.edefg'
MVG
источник
0

Переименовать файлы и каталоги с find -execdir | rename

Если вы собираетесь переименовывать как файлы, так и каталоги не просто с помощью суффикса, тогда это хороший шаблон:

PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
  find . -depth -execdir rename 's/findme/replaceme/' '{}' \;

В отличие от этого, потрясающая -execdirопция делает cdв каталог перед выполнением renameкоманды -exec.

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

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

rename 's/findme/replaceme/g' acc/acc

PATHХакерство требуется , потому что -execdirимеет один очень досадный недостаток: findкрайне самоуверенный и отказывается что - либо делать с , -execdirесли у вас есть какие - либо относительные пути в вашем PATHпеременном окружении, например ./node_modules/.bin, в противном случае с:

find: Относительный путь «./node_modules/.bin» включен в переменную среды PATH, которая небезопасна в сочетании с действием -execdir команды find. Пожалуйста, удалите эту запись из $ PATH

См. Также: Почему использование действия «-execdir» небезопасно для каталога, который находится в переменной PATH?

-execdirявляется расширением GNU find для POSIX . renameоснован на Perl и поставляется из renameпакета. Проверено в Ubuntu 18.10.

Переименовать обходное решение

Если ваши входные пути не взяты find, или если вам надоело относительное раздражение пути, мы можем использовать некоторые возможности Perl для безопасного переименования каталогов, как в:

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

Я не нашел удобный аналог для -execdirс xargs: https://superuser.com/questions/893890/xargs-change-working-directory-to-file-path-before-executing/915686

sort -rТребуется для того , чтобы файлы приходят после их соответствующих каталогов, так как более длинные пути приходят после того, как более короткие, с тем же префиксом.

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
источник