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

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

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

82
задан 20 July 2018 в 04:56

9 ответов

Если все файлы находятся в одной папке

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

Для рекурсивного переименования файлов используйте следующее:

find /path/to/root/folder -type f -name '*.abc' -print0 | xargs -0 rename 's/.abc$/.edefg/'
51
ответ дан 20 July 2018 в 04:56

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

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

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

-тип f означает только поиск файлов, а не каталогов

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

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

    • / marks the end of the "match" part of the regex, а начало "replace" part

    • version1_ add this text to every file name

    • $1 references the existing filename, because we capture it with parentheses. Если вы используете несколько наборов скобок в части "match", вы можете ссылаться на них здесь, используя $2, $3 и т.д.
    • .abc, то новое имя файла будет заканчиваться на .abc. Здесь нет необходимости экранировать метасимвол точки в разделе "replace"
    • / конца регекса

    Before

    tree --charset=ascii
    
    |-- a_file.abc
    |-- Another.abc
    |-- Not_this.def
    `-- dir1
        `-- nested_file.abc
    

    After

    tree --charset=ascii
    
    |-- version1_a_file.abc
    |-- version1_Another.abc
    |-- Not_this.def
    `-- dir1
        `-- version1_nested_file.abc
    

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

    14
    ответ дан 20 July 2018 в 04:56

    Портативный способ (который будет работать на любой 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, который можно комбинировать с глобусом или 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

    98
    ответ дан 20 July 2018 в 04:56

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

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

    ; соответствует нулю или более * / и соответствует # 1 в замена. * соответствует # 2 . Нерекурсивная версия была бы

    mmv '*.abc' '#1.edefg'
    
    2
    ответ дан 20 July 2018 в 04:56

    Другой переносимый способ:

    find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;
    
    5
    ответ дан 20 July 2018 в 04:56

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

    Вот короткая подсказка:

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

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

    0
    ответ дан 20 July 2018 в 04:56

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

    for file in /path/to; do mv $file $(basename -s jpg $file)pdf ; done
    
    0
    ответ дан 20 July 2018 в 04:56

    Переименуйте файлы и каталоги с помощью 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 найти. Удалите эту запись из $ PATH

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

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

    Переименовать обходной путь просмотра вперед

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

    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-execute / 915686

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

    0
    ответ дан 20 July 2018 в 04:56
    # 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

    4
    ответ дан 20 July 2018 в 04:56

    Другие вопросы по тегам:

    Похожие вопросы: