- ВКонтакте
- РћРТвЂВВВВВВВВнокласснРСвЂВВВВВВВВРєРСвЂВВВВВВВВ
- РњРѕР№ Р В Р’В Р РЋРЎв„ўР В Р’В Р РЋРІР‚ВВВВВВВВРЎР‚
- Viber
- Skype
- Telegram
BASH скрипт сжатия PDF - пробелемы со списком файлов
Хочу сжать PDF в каталоге посредством ps2pdf, но не получается настроить цикл for. Постоянно возникают ошибки с именованием файлов.
filelist=$(find . -name \*.pdf)
for i in "$filelist"; do
ps2pdf -dPDFSETTINGS=/ebook "$i"
done
Переменные взяты в кавычки т.к. названия файлов содержат пробелы.
Если в каталоге один файл, то скрипт работает. А если в каталоге больше одного файла, то скрипт выдает ошибку Error: /undefinedfilename in
.
Подскажите, в чем может быть проблема?
Ответы (1 шт):
Когда вы берете $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