Поиск нескольких файлов рекурсивно и переименование в 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
Вы можете использовать это ниже.
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