Переименовать файлы в разных подпапках

Переименовать файлы в разных подпапках

Я хотел бы переименовать несколько файлов, которые находятся в разных папках. А именно, я хотел бы добавить нули в середину имени файла, чтобы все имена файлов имели три цифры (и, следовательно, отображались в логическом порядке).

Если быть точнее, у меня есть 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

Связанный контент