Como fazer um pipeline aguardar o final do arquivo ou parar após um erro?

12

Eu tentei o seguinte comando depois de assistir a este vídeo sobre travessuras de tubos.

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

Basicamente, imprime uma lista de páginas de manual no dmenu para que o usuário selecione uma delas, depois usa xargs para executar man -Tpdf %(imprima para stdout um pdf do manpage git a partir da entrada do xargs) e passe o pdf para um leitor de pdf (zathura )

O problema é que (como você pode ver no vídeo) o leitor de PDF é iniciado antes mesmo de selecionar uma página de manual no dmenu. E se eu clicar em Esc e selecionar nenhum, o leitor de pdf ainda estará aberto, sem nenhum documento.

Como posso fazer com que o leitor de pdf (e qualquer outro comando em uma cadeia de tubulação) seja executado apenas quando sua entrada chegar ao final do arquivo ou quando receber uma entrada? Ou, alternativamente, como posso fazer uma cadeia de tubulações parar depois que um dos comandos encadeados retorna um status de saída diferente de zero (para que, se o dmenu retorne um erro por não selecionar uma opção, os seguintes comandos não sejam executados)?

Seninha
fonte
1
Qual shell você está usando? Isso é festa?
terdon
Eu tentei no bash, zsh e sh. Todos eles tiveram o mesmo comportamento.
Seninha 27/04/19
2
Sim, o comportamento é padrão, perguntei qual shell por causa da pipefailopção do bash mencionada na resposta de Kusalandanda.
terdon

Respostas:

12

Como posso fazer com que o leitor de pdf (e qualquer outro comando em uma cadeia de tubulação) seja executado apenas quando sua entrada chegar ao final do arquivo ou quando receber uma entrada?

Existe ifne(no Debian está no moreutilspacote):

ifne executa o seguinte comando se e somente se a entrada padrão não estiver vazia.

No seu caso:

 | ifne zathura -
Kamil Maciorowski
fonte
Obrigado pela resposta, eu não conhecia este comando! Este comando (e os outros em moreutils) deveria ter sido no Unix original e especificado pelo POSIX ... É uma ferramenta tão básica e Unix-ish ...
Seninha
@Seninha A simplicidade de ifneé um pouco enganadora. O Unix não possui operação "pipe peek", portanto ifnedeve realmente ler pelo menos um byte antes de decidir executar o comando dependente. Isso significa que ele não pode apenas fazer o teste e executar o comando, mas deve criar outro canal, bifurcar outro processo para executar o comando dependente e copiar todo o fluxo do canal stdin para o canal a jusante. Se o caso "entrada vazia" não for comum, ifnepode custar mais recursos do que economiza, em média.
@ Wumpus.Q.Wumbley é um mito - você não precisa ler nenhum byte para determinar se há dados em um pipe. Veja aqui . E no Linux você pode realmente buscar dados de um canal (ou seja, ler os dados sem removê-los). Eu mencionei isso e muito mais em comentários a uma resposta "canônica" aqui, mas eles foram removidos por mods porque eles provavelmente sentiram que esses fatos eram como prejudicar a grandiosidade da resposta.
mosvy
6

Arquivos PDF devem ser procurados; qualquer visualizador de pdf terá que olhar primeiro no trailer e daí saltar para as compensações da tabela refex.

Como os pipes não são procuráveis, zathuraestá usando um truque ofuscante, onde está copiando toda a entrada para um arquivo temporário e, em seguida, use esse arquivo temporário normalmente. Esse tipo de truque "inteligente" está criando falsas esperanças e está levando as pessoas a supor que os arquivos pdf são fáceis de executar.

Mas de qualquer maneira, zathurarealmente faz de espera para o EOF antes de exibir o documento, você não precisa fazer nada para que isso hapen:

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

O problema é que zathuranão há opção de abrir a janela apenas se o arquivo estiver OK e sair com um erro se esse não for o caso - ele permanecerá lá como se estivesse tudo bem:

$ 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?

Portanto, mesmo se você estiver redirecionando a saída para um arquivo temporário e estiver executando apenas zathurase tudo estiver OK, não há garantia de que o usuário não será servido com uma janela preta se zathuranão gostar da saída por um motivo ou outro .


Btw,

man -X man

exibirá uma página de manual em uma janela X11 gxditview, mesmo que pareça diretamente do '70 ;-)

E, é claro, você sempre pode usar:

... | xargs xterm -e man

que, além de muitos outros aprimoramentos, permite usar expressões regulares em pesquisas e seleção de texto adequada.

mosvy
fonte
6

Todos os comandos em um pipeline iniciam praticamente ao mesmo tempo. É apenas a E / S sobre o canal que as sincroniza. Além disso, um tubo pode conter apenas a informação que o buffer do tubo permitir.

Portanto, não é possível evitar a execução de um estágio de um pipeline, porque

  1. o comando nesse estágio é iniciado assim que todos os outros estágios são iniciados, e
  2. se o comando não consumir a entrada que chega pelo canal, ele bloqueará os estágios anteriores do pipeline.

Em vez disso, grave a saída em um arquivo enquanto deixa o pipeline terminar. Então use esse arquivo.

Exemplo (como uma função que aceita um argumento):

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"
}

Além disso, isso não executaria o zathuraprograma se o pipeline falhasse (a xargsparte retornou diferente de zero) ou o arquivo gerado estivesse vazio.

No bashshell, você também pode definir a pipefailopção de shell set -o pipefailpara que o pipeline retorne o status de saída do primeiro comando no pipeline que falhar. E você gostaria de fazer a tmpfilevariável 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"
}

Isso define a pipefailopção para a duração da função, se ainda não estiver definida, e a desativa, se necessário. Ele se livra do -steste no arquivo de saída.

Kusalananda
fonte
1
Por que rm -f? Você está pensando em casos em que o canal altera as permissões do arquivo tmp?
terdon
2
@terdon Estou pensando em casos em que o arquivo temporário é removido prematuramente. rm -fnão erro se o arquivo já foi removido (possivelmente por zathura, eu não sei).
Kusalananda
A primeira função não funciona como esperado: também fará com que o zathura mostre uma janela preta, mas agora o zathura é executado após o término do pipeline, em vez de correr ao lado do pipeline. Isso ocorre porque o pipeline retorna o status de saída de xargs, que é 0. O comando que falha no pipeline é dmenu (que retorna 1 quando eu não seleciono nada). A função bash com a pipefailopção funciona como esperado (e no zsh também, que possui a mesma opção).
Seninha 27/04/19
1
@Seninha Corrigi a primeira função deixando verificar se o arquivo gerado não está vazio.
Kusalananda