Как заставить конвейер ждать окончания файла или останавливаться после ошибки?

12

Я попробовал следующую команду после просмотра этого видео на трубе махинаций.

man -k . | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf | zathura -

Он в основном выводит список man-страниц в dmenu, чтобы пользователь мог выбрать один из них, затем использует xargs для запуска man -Tpdf %(печать выводит PDF-файл man-страницы из ввода xargs) и передает pdf в программу чтения PDF (zathura). ).

Проблема в том, что (как вы можете видеть на видео) программа чтения PDF запускается еще до того, как я выбрал одну страницу в dmenu. И если я нажму «Esc» и не выберу ни один, программа чтения PDF-файлов все еще будет открыта, не показывая никакого документа.

Как я могу заставить программу чтения PDF (и любую другую команду в цепочке конвейеров) запускаться только тогда, когда ее ввод достигает конца файла или когда он вообще получает ввод? Или, в качестве альтернативы, как заставить цепочку конвейера остановиться после того, как одна из цепочечных команд вернет ненулевой статус выхода (так что если dmenu вернет ошибку из-за отсутствия выбора опции, следующие команды не будут выполнены)?

Seninha
источник
1
Какую оболочку вы используете? Это Баш?
Тердон
Я пробовал это на bash, zsh и sh. Все они имели одинаковое поведение.
Сенинха
2
Да, поведение стандартное, я спросил, какая оболочка из-за pipefailопции bash, упомянутой в ответе Кусаланданды.
Terdon

Ответы:

12

Как я могу заставить программу чтения PDF (и любую другую команду в цепочке конвейеров) запускаться только тогда, когда ее ввод достигает конца файла или когда он вообще получает ввод?

Есть ifne(в Debian он есть в moreutilsпакете):

ifne запускает следующую команду тогда и только тогда, когда стандартный ввод не пуст.

В твоем случае:

 | ifne zathura -
Камил Мачоровский
источник
Спасибо за ответ, я не знал эту команду! Эта команда (и остальные в ней moreutils) должна была быть в исходном Unix и определена posix ... Это такой базовый и Unix-иш инструмент ...
Seninha
@Seninha Простота ifneнемного обманчива. Unix не имеет операции "pipe peek", поэтому ifneфактически должен прочитать хотя бы один байт, прежде чем принять решение запустить зависимую команду. Это означает, что он не может просто выполнить тест и выполнить команду, но должен создать другой канал, запустить другой процесс для запуска зависимой команды и скопировать весь поток из канала stdin в канал ниже по потоку. Если случай «ввода пуст» не является распространенным явлением, он ifneможет легко стоить в среднем больше ресурсов, чем экономит.
@ Wumpus.Q.Wumbley, это миф - вам не нужно читать ни одного байта, чтобы определить, есть ли данные в канале. Смотрите здесь . А в Linux вы можете просматривать данные из канала (т.е. читать данные, не удаляя их). Я упомянул это и многое другое в комментариях к «каноническому» ответу здесь, но они были удалены модами, потому что они, вероятно, чувствовали, что эти факты как бы умаляют удивительность ответа.
Мосви
6

PDF-файлы должны быть доступны для поиска; любой просмотрщик PDF должен сначала посмотреть на трейлер, а затем перейти к смещению из таблицы внешних ссылок.

Поскольку каналы недоступны для поиска, zathuraиспользуется запутывающий трюк, при котором все входные данные копируются во временный файл, а затем используется этот временный файл, как обычно. Этот вид «умного» трюка создает ложные надежды и заставляет людей предполагать, что PDF-файлы пригодны для чтения.

Но в любом случае, на zathuraсамом деле делает ожидание для EOF перед отображением документа, вам не нужно ничего делать для того , чтобы hapen:

(sleep 10; cat file.pdf) | zathura -
# will really show the content of file.pdf after 10 seconds

Проблема в том, что zathuraнет возможности открывать окно, только если файл в порядке, и выходить с ошибкой, если это не так, - он просто останется там, как будто все в порядке:

$ dd if=file.pdf bs=50000 count=1 status=none | zathura -
error: could not open document  # its window still hanging around showing nothing

$ echo $?
0  # really?

Таким образом, даже если вы перенаправляете вывод во временный файл самостоятельно и работаете, только zathuraесли все было в порядке, нет гарантии, что пользователю не будет предоставлено черное окно, если zathuraему не нравится вывод по той или иной причине. ,


Btw,

man -X man

отобразит man-страницу в окне X11 gxditview, даже если она выглядит прямо из '70 ;-)

И, конечно же, вы всегда можете использовать:

... | xargs xterm -e man

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

mosvy
источник
6

Все команды в конвейере запускаются практически одновременно. Это только ввод-вывод по каналу, который синхронизирует их. Кроме того, канал может содержать только столько информации, сколько позволяет буфер канала.

Поэтому вы не можете избежать запуска одной стадии конвейера, потому что

  1. команда на этом этапе запускается, как только все остальные этапы запускаются, и
  2. если команда не использует входные данные, поступающие через канал, она заблокирует предыдущие этапы конвейера.

Вместо этого запишите вывод в файл, позволяя конвейеру завершиться. Затем используйте этот файл.

Пример (как функция, принимающая один аргумент):

myman () {
    tmpfile=$( mktemp )

    if man -k "$1" | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf >"$tmpfile" && [ -s  "$tmpfile" ]
    then
        zathura "$tmpfile"
    fi

    rm -f "$tmpfile"
}

Это дополнительно не будет запускать zathuraпрограмму, если конвейер завершится неудачно ( xargsдеталь вернула ненулевое значение) или сгенерированный файл будет пустым.

В bashоболочке вы также можете установить параметр pipefailоболочки так, set -o pipefailчтобы конвейер возвращал состояние выхода первой команды в конвейере, которая завершилась неудачно. И вы хотели бы сделать tmpfileпеременную local:

myman () {
    local tmpfile=$( mktemp )

    if [ -o pipefail ]; then
        set -o pipefail
        trap 'set +o pipefail' RETURN
    fi

    if man -k "$1" | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf >"$tmpfile"
    then
        zathura "$tmpfile"
    fi

    rm -f "$tmpfile"
}

Это устанавливает pipefailпараметр для продолжительности функции, если она еще не была установлена, а затем отменяет ее, если необходимо. Избавляется от -sтеста в выходном файле.

Кусалананда
источник
1
Почему rm -f? Вы думаете о случаях, когда канал изменяет права доступа к tmpfile?
Тердон
2
@terdon Я думаю о случаях, когда временный файл преждевременно удаляется. rm -fне будет ошибки, если файл уже был удален (возможно zathura, я не знаю).
Кусалананда
Первая функция работает не так, как ожидалось: она также заставит затуру показывать черное окно, но теперь затура работает после окончания конвейера, а не вдоль трубопровода. Это связано с тем, что конвейер возвращает состояние выхода xargs, которое равно 0. Команда, которая не выполняется в конвейере, называется dmenu (которая возвращает 1, когда я ничего не выбираю). Функция bash с этой pipefailопцией работает как положено (и в zsh, которая имеет такую ​​же опцию).
Сенинья,
1
@Seninha Я исправил первую функцию, позволив ей проверить, не является ли сгенерированный файл непустым.
Кусалананда