Bash переместить и переименовать, анализируя вывод find

У меня есть вложенные каталоги файлов 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 ответ

Решение

Пытаться:

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 это просто заполнитель. Он назначен $0в соглашениях bash - это имя команды, которую мы запускаем.

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

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

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

More efficient version

The above version invoke bash for every file found. Alternatively, we can invoke bash just once for several files found. To do this, we wrap our command in a for цикл:

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

Alternate problem

Suppose that all the files we want are in a second level directory and we want the moved files to have the order of the directory names reversed so that instead of ./path1_pathA_fileI.pdf, we end up with ./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

Or, for those who prefer their commands spread out over multiple lines:

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
Другие вопросы по тегам