Como você determina o comando real que está sendo canalizado para você?

9

Digamos que eu tenho um script bash chamado log.sh. Neste script, quero ler a entrada de um canal, mas também quero saber o comando usado para canalizar a entrada em mim. Exemplo:

tail -f /var/log/httpd/error | log.sh

No script shell, quero conhecer o comando tail -f /var/log/httpd/error.

St. John Johnson
fonte
Estou extremamente curioso para saber por que você quer isso.
Ignacio Vazquez-Abrams
Meu palpite é que você está criando algum tipo de programa GUI que captura e processa pids?
11601 Palbakulich
Gostaria de saber para poder distinguir onde colocar os resultados. Dependendo do comando e do nome do arquivo, eu gostaria de executar ações diferentes.
3111 John St. Johnson
4
Isso parece uma grande violação do princípio da menor surpresa para mim. Se o script deve fazer coisas diferentes em circunstâncias diferentes, isso deve ser controlado por uma opção de linha de comando, e não pela origem da entrada.
Dave Sherohman
@ Dave, eu concordo. Digamos que, para o bem deste exemplo, eu apenas quero 'saber' qual é o comando recebido.
3111 John St. Johnson

Respostas:

7

Akira sugeriu o uso lsof.

Aqui está como você pode criar um script:

whatpipe2.sh

#!/bin/bash

pid=$$
pgid=$(ps -o pgid= -p $pid)
lsofout=$(lsof -g $pgid)
pipenode=$(echo "$lsofout" | awk '$5 == "0r" { print $9 }')
otherpids=$(echo "$lsofout" | awk '$5 == "1w" { print $2 }')
for pid in $otherpids; do
    if cmd=$(ps -o cmd= -p $pid 2>/dev/null); then
        echo "$cmd"
        break
    fi
done

Executando:

$ tail -f /var/log/messages | ./whatpipe2.sh
tail -f /var/log/messages
^C

Outra maneira é usar grupos de processos.

whatpipe1.sh

#!/bin/bash    

pid=$$
# ps output is nasty, can (and usually does) start with spaces
# to handle this, I don't quote the "if test $_pgrp = $pgrp" line below
pgrp=$(ps -o pgrp= -p $pid)
psout=$(ps -o pgrp= -o pid= -o cmd=)
echo "$psout" | while read _pgrp _pid _cmd; do
    if test $_pgrp = $pgrp; then
        if test $_pid != $pid; then
            case $_cmd in
            ps*)
                # don't print the "ps" we ran to get this info
                # XXX but this actually means we exclude any "ps" command :-(
                ;;
            *)
                echo "$_cmd"
                ;;
            esac
        fi
    fi
done

Executando:

$ tail -f /var/log/messages | ./whatpipe1.sh
tail -f /var/log/messages
^C

Observe que os dois só funcionam se o comando no lado esquerdo do tubo for executado por tempo suficiente para psvê-lo. Você disse que estava usando tail -f, então duvido que isso seja um problema.

$ sleep 0 | ./whatpipe1.sh 

$ sleep 1 | ./whatpipe1.sh
sleep 1
Mikel
fonte
em vez deste post enorme, eu daria uma segunda resposta com o script baseado em lsof. bom trabalho para esse.
akira
@akira Obrigado. Foram necessárias algumas tentativas para torná-lo limpo e portátil. Aprendi algumas coisas sobre procfs e lsof ao longo do caminho. Obrigado pela ideia.
Mikel
Aceitei o seu, pois ele fornece uma resposta que outras pessoas podem usar diretamente. @Akira, você fez a maior parte do trabalho, desculpe-me por não aceitar o seu também.
3111 John St. Johnson
10

o canal aparecerá como uma entrada na lista de descritores de arquivos abertos do seu processo:

 % ls -l /proc/PID/fd
 lr-x------ 1 xyz xyz 64 Feb 11 08:05 0 -> pipe:[124149866]
 lrwx------ 1 xyz xyz 64 Feb 11 08:05 1 -> /dev/pts/2
 lrwx------ 1 xyz xyz 64 Feb 11 08:05 2 -> /dev/pts/2
 lr-x------ 1 xyz xyz 64 Feb 11 08:05 10 -> /tmp/foo.sh

você também pode usar algo como:

 % lsof -p PID
 sh      29890 xyz  cwd    DIR   0,44    4096  77712070 /tmp
 sh      29890 xyz  rtd    DIR   0,44    4096  74368803 /
 sh      29890 xyz  txt    REG   0,44   83888  77597729 /bin/dash
 sh      29890 xyz  mem    REG   0,44 1405508  79888619 /lib/tls/i686/cmov/libc-2.11.1.so
 sh      29890 xyz  mem    REG   0,44  113964  79874782 /lib/ld-2.11.1.so
 sh      29890 xyz    0r  FIFO    0,6         124149866 pipe
 sh      29890 xyz    1u   CHR  136,2                 4 /dev/pts/2
 sh      29890 xyz    2u   CHR  136,2                 4 /dev/pts/2
 sh      29890 xyz   10r   REG   0,44      66  77712115 /tmp/foo.sh

então, do que você tem o inode do pipe :) agora você pode pesquisar todos os outros processos sob /proc/esse pipe. então você terá o comando que está direcionando para você:

 % lsof | grep 124149866 
 cat     29889 xyz    1w  FIFO                0,6          124149866 pipe
 sh      29890 xyz    0r  FIFO                0,6          124149866 pipe

neste exemplo, catcanalizado para as enfermarias sh. em /proc/29889você pode encontrar um arquivo chamado cmdlineque informa exatamente o que foi chamado:

 % cat /proc/29889/cmdline
 cat/dev/zero%  

os campos da linha de comando são separados por NUL, portanto, parece um pouco feio :)

akira
fonte
Não sei qual resposta aceitar. @Akira, você deu a descrição real de como determiná-lo, enquanto @Mikel me deu um script. Maneira de tornar as coisas incríveis + difíceis.
3111 John St. Johnson
1

Aqui está uma solução compacta que usa lsofdistribuições modernas e modernas do Linux:

cmd=$(lsof -t -p $$ -a -d 0 +E | while read p; do
    [ $p -ne $$ ] && echo "$(tr \\000 " " </proc/$p/cmdline)"
done)

Isso lista os arquivos de terminal ( +E) do FD 0 no processo atual do shell ( -p $$ -a -d 0) e limita a saída apenas aos PIDs ( -t), produzindo os PIDs nos dois lados do tubo.

Observe que:

  1. Pode haver mais de um PID encontrado no final da fonte, por exemplo { echo Hi; sleep 5 ; } | whatpipe.sh, provavelmente produzirá um bash(subshell de entrada) e sleep 5.
  2. +Eestá disponível apenas se lsoffoi compilado com -DHASUXSOCKEPT. Isso deve ser verdade para as distribuições mais modernas do Linux, mas verifique sua instalação de qualquer maneira com:lsof -v 2>&1 | grep HASUXSOCKEPT
Adrian
fonte