BASH скрипт сжатия PDF - пробелемы со списком файлов

Хочу сжать PDF в каталоге посредством ps2pdf, но не получается настроить цикл for. Постоянно возникают ошибки с именованием файлов.

filelist=$(find . -name \*.pdf)
for i in "$filelist"; do
    ps2pdf -dPDFSETTINGS=/ebook "$i"
done

Переменные взяты в кавычки т.к. названия файлов содержат пробелы.

Если в каталоге один файл, то скрипт работает. А если в каталоге больше одного файла, то скрипт выдает ошибку Error: /undefinedfilename in.

Подскажите, в чем может быть проблема?


Ответы (1 шт):

Автор решения: Vitalizzare

Когда вы берете $filelist в кавычки, то он становится одним словом. Соответственно цикл выполняется один раз, где переменная цикла равна всему списку найденных файлов. Отсюда ошибка undefinedfilename.

Как быть? Вариант первый, выполнить ps2pdf внутри find:

find . -name \*.pdf -exec ps2pdf -dPDFSETTINGS=/ebook {} \;

Вариант второй, использовать while read вместо for. По умолчанию, find возвращает имена файлов построчно, тогда как read считывает одну строку за подход. То есть, в таком цикле будут поочередно обрабатываться строки, а не слова, как в цикле for i in words:

find . -name \*.pdf | while read f; do ps2pdf -dPDFSETTINGS=/ebook "$f"; done

Вариант третий, установить IFS=$'\n' на время выполнения цикла for i in words, чтобы словом считался участок между символами перевода строки:

IFS=$'\n' eval 'for i in $(find . -name \*.pdf); do ps2pdf -dPDFSETTINGS=/ebook "$i"; done'

P.S. Если в имени файла использован перевод строки, то мы лишаемся возможности отслеживать имена по символу \n. В этом случае, кроме очевидных ответов (выполнить команду внутри find, как предложено в первом варианте, или переименовать файлы) можно дать указание find использовать ноль вместо \n как метку конца имени, а результат перенаправить команде xargs -0:

find . -name \*.pdf -print0 | xargs -0I{} ps2pdf -dPDFSETTINGS=/ebook {}

или дополнить read указанием $'\0' как разделителя строк:

find ... -print0 | while read -d $'\0' f; do ...

Имена файлов не могут содержать код 0x00 и такой подход представляется надежным. Однако мы не можем сохранить этот результат в переменной (нули при сохранении удаляются, вероятно, из-за их использования как окончания строк внутри системы). С другой стороны, если мы хотим сохранить список в переменную и использовать позже в цикле for, то можем пойти на компромисс, предположив, что вряд ли кто-то по ошибке и без злого умысла будет использовать в именах файлов некоторые управляющие коды (например вертикальную табуляцию, звонок, маркеры полей для старых накопителей, код Delete) и задать один из них как разделитель с помощью параметра -printf. Возьмем, в порядке эксперимента, звонок (BEL, \a, \007) и создадим файлы с пробелом и переводом строки (можно создать и со звонком, но тут уж как в анекдоте "Ты кому друг, мне или медведю?"). Ниже приведена последовательность команд и их результат без привязки к исходному вопросу, чтобы посмотреть на живом примере, как это происходит:

# Prepare an experimental space 
> cd `mktemp -d`
> touch 'a b' x$'\n'y

# Do the job
> files=$(find . -type f -printf '%p\a')    # Terminate paths with BEL symbol
> echo -n "$files" | xxd
00000000: 2e2f 780a 7907 2e2f 6120 6207            ./x.y../a b.
                    # ^^-------------^^------ Notice the separating BEL symbol
> IFS=$'\a' eval 'for f in $files; do echo "$((++count))) $f"; done'
1) ./x
y
2) ./a b

# Clear the space
> cd -
> rm -ir $OLDPWD
→ Ссылка