4

У меня есть вложенные каталоги файлов PDF, и я хотел бы извлечь их в каталог более высокого уровня, переименовав их следующим образом:

Мои файлы что-то вроде:

./path1/pathA/fileI.pdf
./path1/pathB/fileII.pdf

Я хочу добиться:

./path1_pathA_fileI.pdf    
./path1_pathB_fileII.pdf

Я знаю, что могу сделать список файлов, выполнив

find . -type f -name "*.pdf"

И я могу представить решение, используя

find . -type f -name "*.pdf" | mv -t ...

Но я не знаю, как заполнить ... потому что я не понимаю парсинга и назначения переменных в bash. Как разделить путь на «/» и сформировать новый путь и имя файла, как указано выше?

Спасибо заранее!

1 ответ1

6

Пытаться:

find . -mindepth 2 -type f -name '*.pdf' -exec bash -c 'f=${1#./}; mv "$1" "./${f//\//_}"' None {} \;

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

Как это устроено

  • -mindepth 2

    Это говорит find не обрабатывать файлы, которые уже находятся в текущем каталоге.

  • -type f -name '*.pdf'

    Это ограничивает поиск обычными файлами с расширением pdf .

  • -exec bash -c '...' None {} \;

    Это запускает команду в строке в кавычках, предоставляя имя файла в качестве первого аргумента, $1 .

    Для наших целей строка None является просто заполнителем. В соответствии с соглашениями bash он присваивается $0 - это имя команды, которую мы запускаем.

  • f=${1#./}; mv "$1" "./${f//\//_}"

    Это (а) удаляет префикс ./ из имени файла, и (б) перемещает файл в нужное место с новым именем.

    ${1#./} Является примером удаления префикса bash. Возвращает стринг $1 с удалением ./ с начала. ${f//\//_} является примером замены шаблона в bash. Возвращает строку $f со всеми / заменёнными на _ . Чтобы узнать больше об этих функциях, смотрите раздел man bash озаглавленный Расширение параметров .

Более эффективная версия

Приведенная выше версия вызывает bash для каждого найденного файла. В качестве альтернативы, мы можем вызвать bash только один раз для нескольких найденных файлов. Для этого мы заключаем нашу команду в цикл for :

find . -mindepth 2 -type f -name '*.pdf' -exec bash -c 'for f in "$@"; do f=${f#./}; echo mv "$f" "./${f//\//_}"; done' None {} +

Альтернативная проблема

Предположим, что все нужные нам файлы находятся в каталоге второго уровня, и мы хотим, чтобы перемещенные файлы имели обратный порядок имен каталогов, чтобы вместо ./path1_pathA_fileI.pdf мы ./pathA_path1_fileI.pdf . В этом случае:

for d1 in */; do d1=${d1%/}; for d2 in "$d1"/*/; do d2=${d2%/}; p="${d2#$d1/}_$d1"; for f in "./$d2"/*.pdf; do echo mv "$f" "./${p}_${f#./$d2/}"; done; done; done

Или для тех, кто предпочитает, чтобы их команды распределялись на несколько строк:

for d1 in */
do
    d1=${d1%/}
    for d2 in "$d1"/*/
    do
         d2=${d2%/}
         p="${d2#$d1/}_$d1"
         for f in "./$d2"/*.pdf
         do
             echo mv "$f" "./${p}_${f#./$d2/}"
         done
    done
done

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .