
Я хотел бы переименовать несколько файлов, которые находятся в разных папках. А именно, я хотел бы добавить нули в середину имени файла, чтобы все имена файлов имели три цифры (и, следовательно, отображались в логическом порядке).
Если быть точнее, у меня есть 130+ папок с 400+ .nii файлами в каждой папке. Каждый .nii файл имеет следующий шаблон:
swu_run1_P_139_Vol_1.nii
- P_### варьируется от P76 до P277 (номер участника)
- Vol_### варьируется от 1 до 405 (номер тома)
Так как объем варьируется от 1 до 405, это означает, что любой список начинается со 100, а не идет от 1 до 405 (например, он начинается: 100 - 101 - 102 [..] - 109 -10- 110 - 111 и т. д.) Один из способов решения этой проблемы — добавить нули к имени файла и сделать все трехзначным, например:
swu_run1_P_277_Vol_1.nii -> swu_run1_P_277_Vol_001.nii
swu_run1_P_277_Vol_2.nii -> swu_run1_P_277_Vol_002.nii
swu_run1_P_277_Vol_10.nii -> swu_run1_P_277_Vol_010.nii
swu_run1_P_277_Vol_120.nii -> swu_run1_P_277_Vol_120.nii
У меня мало опыта работы с системами unix/linux, но, используя предыдущие темы, мне удалось придумать следующий код. Он состоит из двух частей:
1. Переименуйте несколько имен файлов, добавив нули в середину имени файла:
rename Vol_ Vol_0 *Vol_[0-9].nii
Если я запускаю это в подпапке, я получаю следующее сообщение об ошибке:
Error: rename: swu_run1_P_275_Vol_1.nii: rename to swu_run1_P_275_Vol_01.nii failed: No such file or directory
Как ни странно, он добавляет ноль к Vol_1-9. Однако он не добавляет ноль к любому числу, которое уже имеет две или три цифры:
swu_run1_P_277_Vol_1.txt -> swu_run1_P_277_Vol_01.nii
swu_run1_P_277_Vol_10.txt -> swu_run1_P_277_Vol_10.nii
swu_run1_P_277_Vol_100.txt -> swu_run1_P_277_Vol_100.nii
Похоже, что в выражении происходит какой-то странный цикл (оно пытается изменить новый Vol_01, выдавая сообщение об ошибке)? И почему оно не добавляет ноль к двум/трем цифрам?
2. Найдите все .nii
файлы в соответствующих подпапках, а затем выполните пакетное переименование:
find . -iname "*.nii" -execdir rename Vol_ Vol_0 *Vol_[0-9].nii '{}' \;
Мое понимание этого кода следующее:
find . -iname "*.nii"
ищет все файлы .nii как в текущей папке, так и во вложенных папках-execdir
сообщает ему, что необходимо применить следующее выражение к текущей папке и подпапкам, т.е. добавить нольrename Vol_ Vol_0 *Vol_[0-9].nii
добавляет этот ноль (используя форматизтекста печатать список файлов)'{}'
есть ли путь к имени файла\;
есть ли конец выражения -execdir
Если я попытаюсь запустить код в большем количестве папок, я получу следующее сообщение об ошибке:
Error: rename: *Vol_[0-9].nii: rename to *Vol_0[0-9].nii failed: No such file or directory
Думаю, я получаю сообщение об ошибке из-за того, что -execdir не выполняется в подпапках, но не могу понять, как решить эту проблему.
Я бы предпочел не заходить в каждую подпапку вручную, чтобы запустить оболочку, так что есть ли у вас предложения, как улучшить этот код (и заставить его работать)? И почему я получаю сообщение об ошибке "Нет такого файла или каталога"?
решение1
swu_run1_P_277_Vol_1.nii
Если бы в переменной было имя name
, то ${name##*_}
было бы 1.nii
(самая длинная соответствующая строка префикса *_
удаляется).
Берем его и удаляем, .nii
и получаем число, которое мы хотим заполнить нулями до ширины в три символа:
name=swu_run1_P_277_Vol_1.nii
number=${name##*_}
number=${number%.nii}
В bash
оболочке самый простой способ (возможно) заполнить число нулями — это printf
:
printf '%.3d' "$number" # would print 001 (with no newline)
Мы можем одновременно сконструировать новое имя:
printf '%s_%.3d.nii' "${name%_*.nii}" "$number"
Эта printf
команда выведет результат, swu_run1_P_277_Vol_001.nii
удалив строку суффикса, соответствующую _*.nii
исходному имени, добавив _
заполненное нулями число, а затем .nii
строку суффикса.
В довершение всего мы можем вывести полученную строкунапрямуюв новую переменную с помощью printf -v newname ...
.
Объединяем это для одного имени:
name=swu_run1_P_277_Vol_1.nii
number=${name##*_}
number=${number%.nii}
printf -v newname '%s_%.3d.nii' "${name%_*.nii}" "$number"
Тогда это просто вопрос mv "$name" "$newname"
.
Хорошо, а как это сделать для всех соответствующих файлов?
Предположим, что все соответствующие файлы соответствуют шаблону подстановки *Vol_*.nii
, тогда, с помощью find
,
find . -type f -name '*Vol_*.nii' -exec bash -c '
for pathname do
dirpath=${pathname%/*} # or: dirpath=$(dirname "$pathname")
name=${pathname##*/} # or: name=$(basename "$pathname")
number=${name##*_}
number=${number%.nii}
printf -v newname "%s_%.3d.nii" "${name%_*.nii}" "$number"
printf "Would rename %s --> %s\n" "$pathname" "$dirpath/$newname"
# mv "$pathname" "$dirpath/$newname"
done' bash {} +
Встроенный bash -c
скрипт здесь вызывается find
с пакетами найденных путей, которые соответствуют критериям -type f
(является обычным файлом) и -name
(имеет определенное имя). Скрипт циклически проходит по этим путям и вытаскивает имя файла в , name
а путь к каталогу в dirpath
.
Затем он выполняет те же операции, что и раньше, чтобы получить новое имя, которое сохраняется в newname
, а затем переименовывает файл.
Ну, я закомментировал саму mv
команду для безопасности. Вам следует запустить ее один раз и сначала убедиться, что вывод правильный. Если вы используете инструменты GNU, вы также можете использовать mv -b
для создания резервных копий файлов, если есть конфликты имен.
В качестве примечания, в zsh
оболочке шаблон подстановки
./**/*Vol_*.nii(n)
расширится до всех этих именв числовом порядке(и рекурсивно вниз по подкаталогам int):
$ print -rC1 ./**/*Vol_*.nii(n)
./swu_run1_P_277_Vol_1.nii
./swu_run1_P_277_Vol_2.nii
./swu_run1_P_277_Vol_10.nii
./swu_run1_P_277_Vol_120.nii