У меня есть несколько каталогов внутри каталога, который мне нужно переименовать, включив в начало имени символ подчеркивания «_».
В качестве теста я создал родительский каталог, содержимое которого состоит из трех каталогов:
dir1, dir2 and dir3; three files: file1, file2, file3
и три файла с точечным префиксом:
.file1, .file2, .file3
До сих пор я пробовал использовать gfind
, после установки findutils
на Mac:
gfind . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh
Это возвращает:
mv: rename ./. to ./_.: Invalid argument
Итак, я попробовал использовать rename с find:
find . -type d -exec rename 's/^/_/' {} \;
Это возвращает:
Can't rename '.' to '_.': Invalid argument
Can't rename './dir1' to '_./dir1': No such file or directory
Can't rename './dir2' to '_./dir2': No such file or directory
Can't rename './dir3' to '_./dir3': No such file or directory
Может, у кого-нибудь есть действенное решение или кто-нибудь знает, где я могу ошибаться?
решение1
Если я правильно понял, у вас есть такие файлы и каталоги:
$ mkdir dir{1,2,3} ; touch file{1,2,3} .file{1,2,3}
$ ls -A
.file1 .file2 .file3 dir1 dir2 dir3 file1 file2 file3
и хотите переименовать каталоги в _dir1
, _dir2
, _dir3
?
Это find
/ printf mv
выглядит выполнимым. Давайте посмотрим, что он напечатает:
$ find . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n"
mv "./." "./_."
mv "./dir2" "./_dir2"
mv "./dir3" "./_dir3"
mv "./dir1" "./_dir1"
Первая команда выдаст ошибку, так как "точка" является специальной и вы не можете ее переименовать. Но остальные должны быть в порядке и должны переименовать каталоги так, как вы хотели:
$ find . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh
mv: cannot move ‘./.’ to ‘./_.’: Device or resource busy
$ ls -d _dir*
_dir1 _dir2 _dir3
Однако передача команд в оболочку немного некрасива, и если имена файлов достаточно странные, результаты будут неожиданными (например, если они содержат $
знаки, которые вызовут подстановку переменных или команд в оболочке).
Если все файлы находятся на одном уровне, то это должно сработать:
for x in * ; do [ -d "$x" ] && mv "$x" "_$x" ; done
(Хотя если _$x
он уже существует, $x
то будет перенесен туда.)
Если вы хотите включить каталоги, имена которых начинаются с точки, используйте shopt -s dotglob
beforehand.
Это тоже близко:
find . -type d -exec rename 's/^/_/' {} \;
Но поскольку find
дает rename
пути, начинающиеся с ./
, нам нужно это учитывать. Только на одном уровне это должно сделать (изменяет лидирующий ./
на ./_
):
find . -type d -exec rename 's,^./,./_,' {} \;
Чтобы получить каталоги на всех уровнях, find -execdir
может быть проще всего использовать. Он запускает команду в каталоге файла. Нам нужно -depth
обработать переименования в правильном порядке.
find . -depth -type d -execdir rename 's,^./,./_,' {} \;
Может быть, также добавить ! -name .
. например
$ mkdir foo foo/dir1 foo/dir2
$ find . -depth \! -name . -type d -execdir rename 's,^./,./_,' {} \;
$ ls _foo
_dir1 _dir2
решение2
Проблема с первой попыткой в том, что вы пытаетесь переименовать и .
саму папку (текущую папку), и все подпапки. Пропустите ее.
gfind . -mindepth 1 -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh
Но на самом деле не делайте этого. Никогда не передавайте данные в sh
.Это не работает.Если только ваши имена файлов не содержат никаких специальных символов оболочки. Если у вас есть файл с именем `rm -r ~`
(совершенно допустимое, хотя и необычное, имя файла), то pop переходит в ваш домашний каталог. Вызовите оболочку, -exec
как я показываю ниже.
Ваша вторая попытка не сработала, потому что вы вставляете _
в начало полного пути, вместо того чтобы добавить его в начало базового имени, т. е. после последнего /
.
find . -mindepth 1 -type d -exec rename 's![^/]+$!_$&!' {} \;
Ни один из них на самом деле не работает, если у вас есть вложенные каталоги, потому что они переименовывают каталоги, пока find
они проходят по ним. Когда вы делаете это, вам нужно действовать с содержимым каталога, прежде чем действовать с самим каталогом, с опцией -depth
.
find . -mindepth 1 -depth -type d -exec rename 's![^/]+$!_$&!' {} \;
Более переносимо, вызовите оболочку и запустите mv
. Это работает на любой системе POSIX.
find . -depth -type d -name . -o -exec sh -c '
for d do mv "$d" "${d%/*}/_${d##*/}"; done
' sh {} +
Если у вас нет вложенных подкаталогов, то вы можете использовать простой цикл оболочки. Если вы не хотите переименовывать каталоги, имена которых начинаются с точки, просто используйте подстановочный знак */
, который соответствует только каталогам и символическим ссылкам на каталоги.
for d in */; do
if [ "$d" = "*/* ] && [ ! -e "$d" ]; then continue; fi # robustness in case there are no subdirectories
if [ -L "$d" ]; then continue; fi # optional: skip symbolic links to directories
mv -- "$d" "_${d%/}"
done
Если вы хотите переименовать все каталоги, включая те, имена которых начинаются с точки, то .*
также включите подстановочный знак.
for d in .*/ */; do
if [ "$d" = "*/* ] && [ ! -e "$d" ]; then continue; fi
if [ -L "$d" ]; then continue; fi
mv -- "$d" "_${d%/}"
done
Обратите внимание, что при перемещении foo
в _foo
, если уже существует каталог с именем _foo
, старый каталог будет перемещен foo
в _foo/foo
. Чтобы защититься от этого, добавьте явную проверку.
Или, как вариант, используйте zsh. Он предустановлен на macOS. Сначала запустите autoload -U zmv
(вставьте это в свой .zshrc
или в свой скрипт), чтобы загрузитьzmv
функция, то нерекурсивная версия (включая файлы точек, исключая символические ссылки — (/D)
это наборквалификаторы glob):
zmv -Q '*(/D)' '_$f'
или рекурсивная версия (с использованиеммодификаторыдля извлечения имени каталога и базового имени):
zmv -Q '**/*(/D)' '$f:h/_$f:t'
zmv
откажется делать ход, если цель уже существует.