Поиск нескольких файлов рекурсивно и переименование в Linux

У меня такие файлы как a_dbg.txt, b_dbg.txt ... в Suse 10 система. Я хочу написать скрипт оболочки bash, который должен переименовать эти файлы, удалив из них "_dbg".

Google предложил мне использовать rename команда. Поэтому я выполнил команду rename _dbg.txt .txt *dbg* на CURRENT_FOLDER

Мой актуальный CURRENT_FOLDER содержит следующие файлы.

CURRENT_FOLDER/a_dbg.txt
CURRENT_FOLDER/b_dbg.txt
CURRENT_FOLDER/XX/c_dbg.txt
CURRENT_FOLDER/YY/d_dbg.txt

После выполнения rename команда,

CURRENT_FOLDER/a.txt
CURRENT_FOLDER/b.txt
CURRENT_FOLDER/XX/c_dbg.txt
CURRENT_FOLDER/YY/d_dbg.txt

Не делать рекурсивно, как заставить эту команду переименовывать файлы во всех подкаталогах. подобно XX а также YY У меня будет так много подкаталогов, название которых непредсказуемо. А также мой CURRENT_FOLDER будет иметь некоторые другие файлы также.

13 ответов

Решение

Ты можешь использовать find найти все подходящие файлы рекурсивно:

$ find . -iname "*dbg*" -exec rename _dbg.txt .txt '{}' \;

РЕДАКТИРОВАТЬ: что '{}' а также \; являются?

-exec аргумент заставляет поиск выполнить rename для каждого найденного файла. '{}' будет заменен на путь к файлу. Последний жетон, \; есть только для того, чтобы отметить конец выражения exec.

Все, что хорошо описано в справочной странице для поиска:

 -exec utility [argument ...] ;
         True if the program named utility returns a zero value as its
         exit status.  Optional arguments may be passed to the utility.
         The expression must be terminated by a semicolon (``;'').  If you
         invoke find from a shell you may need to quote the semicolon if
         the shell would otherwise treat it as a control operator.  If the
         string ``{}'' appears anywhere in the utility name or the argu-
         ments it is replaced by the pathname of the current file.
         Utility will be executed from the directory from which find was
         executed.  Utility and arguments are not subject to the further
         expansion of shell patterns and constructs.

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

find -iname \*.* | rename -v "s/ /-/g"

Небольшой скрипт, который я написал, чтобы заменить все файлы с расширением.txt на расширение.cpp в /tmp и подкаталогах рекурсивно

#!/bin/bash

for file in $(find /tmp -name '*.txt')
do
  mv $file $(echo "$file" | sed -r 's|.txt|.cpp|g')
done

С bash:

shopt -s globstar nullglob
rename _dbg.txt .txt **/*dbg*

Переименовать файлы и каталоги с 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

Смотрите также: https://askubuntu.com/questions/621132/why-using-the-execdir-action-is-insecure-for-directory-which-is-in-the-path/1109378

-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 требуется, чтобы файлы следовали за соответствующими каталогами, поскольку более длинные пути идут после более коротких с тем же префиксом.

Скипет выше может быть записан в одну строку:

find /tmp -name "*.txt" -exec bash -c 'mv $0 $(echo "$0" | sed -r \"s|.txt|.cpp|g\")' '{}' \;

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

#on current folder
rnm -dp -1 -fo -ssf '_dbg' -rs '/_dbg//' *

-dp -1 сделает его рекурсивным для всех подкаталогов.

-fo подразумевает режим только для файлов.

-ssf '_dbg' ищет файлы с _dbg в имени файла.

-rs '/_dbg//' заменяет _dbg пустой строкой.

Вы можете запустить вышеупомянутую команду с путем CURRENT_FOLDER тоже:

rnm -dp -1 -fo -ssf '_dbg' -rs '/_dbg//' /path/to/the/directory

Если кому-то удобно использовать fd и rnr , команда такова:

      fd -t f -x rnr '_dbg.txt' '.txt'

rnr единственная команда:

      rnr -f -r '_dbg.txt' '.txt' *

rnrимеет то преимущество, что может отменить команду.

Вы можете использовать это ниже.

rename --no-act 's/\.html$/\.php/' *.html */*.html

Эта команда сработала для меня, помните, сначала установите пакет переименования:

      find -iname \*.* | grep old-name | rename -v "s/oldname/newname/g

Чтобы расширить отличный ответ @CiroSantilliПутлерКапут六四事: не сопоставляйте файлы в поиске, которые нам не нужно переименовывать.

Я обнаружил, что это значительно повышает производительность Cygwin.

Пожалуйста, не стесняйтесь исправлять мою неэффективную кодировку bash.

      FIND_STRING="ZZZZ"
REPLACE_STRING="YYYY"

FIND_PARAMS="-type d"

find-rename-regex() (
  set -eu
  find_and_replace="${1}/${2}/g"
  echo "${find_and_replace}"
  find_params="${3}"
  mode="${4}"
  if [ "${mode}" = 'real' ]; then
    PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
      find . -depth -name "*${1}*" ${find_params} -execdir rename -v "s/${find_and_replace}" '{}' \;
  elif [ "${mode}" = 'dryrun' ]; then
    echo "${mode}"
    PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
      find . -depth -name "*${1}*" ${find_params} -execdir rename -n "s/${find_and_replace}" '{}' \;
  fi
)

find-rename-regex "${FIND_STRING}" "${REPLACE_STRING}" "${FIND_PARAMS}" "dryrun"
# find-rename-regex "${FIND_STRING}" "${REPLACE_STRING}" "${FIND_PARAMS}" "real"

В Ubuntu ( после установки переименования) это более простое решение сработало для меня лучше всего. Это заменяет пробел на подчеркивание, но может быть изменено по мере необходимости.

find . -depth | rename -d -v -n "s/ /_/g"

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

Флаг -d при переименовании сообщает, что нужно переименовывать только компонент имени файла пути. Я не знаю, насколько общее это поведение, но в моей установке ( Ubuntu 20.04) это может быть файл или каталог, если он является конечным узлом пути.

Я рекомендую сначала флаг -n (без действия) вместе с -v, чтобы вы могли видеть, что будет переименовано и как.

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

Классическое решение:

for f in $(find . -name "*dbg*"); do mv $f $(echo $f | sed 's/_dbg//'); done
Другие вопросы по тегам