¿Cómo se determina el comando real que está entrando en ti?

9

Digamos que tengo un script bash llamado log.sh. En este script, quiero leer la entrada de una tubería, pero también quiero saber el comando utilizado para canalizar la entrada en mí. Ejemplo:

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

En el script de shell, quiero saber el comando tail -f /var/log/httpd/error.

San Juan Johnson
fuente
Tengo mucha curiosidad de por qué quieres esto.
Ignacio Vazquez-Abrams
Supongo que estás haciendo algún tipo de programa GUI que captura y procesa pids
palbakulich
Me gustaría saber para poder distinguir dónde colocar los resultados. Dependiendo del comando y el nombre del archivo, me gustaría realizar diferentes acciones.
St. John Johnson
44
Eso me parece una violación importante del Principio de Menos Sorpresa. Si el script debe hacer cosas diferentes en circunstancias diferentes, entonces eso debe ser controlado por una opción de línea de comandos en lugar de por dónde proviene su entrada.
Dave Sherohman
@ Dave, estoy de acuerdo. Digamos, por el bien de este ejemplo, solo quiero 'saber' cuál es el comando entrante.
St. John Johnson

Respuestas:

7

Akira sugirió usar lsof.

Así es como puedes escribirlo:

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

Ejecutándolo:

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

Otra forma es usar grupos de procesos.

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

Ejecutándolo:

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

Tenga en cuenta que ambos solo funcionan si el comando en el lado izquierdo de la tubería se ejecuta durante el tiempo suficiente para psverlo. Dijiste que lo estabas usando tail -f, así que dudo que esto sea un problema.

$ sleep 0 | ./whatpipe1.sh 

$ sleep 1 | ./whatpipe1.sh
sleep 1
Mikel
fuente
en lugar de esta gran publicación, habría dado una segunda respuesta con el script basado en lsof. buen trabajo para ese.
akira
@akira Gracias. Tomó algunos intentos para hacerlo limpio y portátil. En el camino aprendí algunas cosas sobre procfs y lsof. Gracias por la idea
Mikel
Acepté el tuyo ya que da una respuesta que otras personas pueden usar directamente. @ Akira, hiciste la mayor parte del trabajo, lo siento, no pude aceptar el tuyo también.
St. John Johnson
10

la tubería aparecerá como una entrada en la lista de descriptores de archivos abiertos de su proceso:

 % 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

también podrías 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

entonces, que tiene el inodo de la tubería :) ahora puede buscar en cualquier otro proceso debajo /proc/de esa tubería. entonces tendrás el comando que te está canalizando:

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

en este ejemplo, catcanalizado a las salas sh. en /proc/29889puedes encontrar un archivo llamado cmdlineque te dice exactamente cómo se llamó:

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

los campos de la línea de comando están separados por NUL, por lo que se ve un poco feo :)

akira
fuente
No sé qué respuesta aceptar. @ Akira, usted dio el desglose real de cómo determinarlo, mientras que @ Mikel me dio un guión. Manera de hacer las cosas increíbles + difíciles.
St. John Johnson
1

Aquí hay una solución compacta que utiliza lsofdistribuciones modernas en Linux modernas:

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

Esto enumera los archivos de punto final ( +E) de FD 0 en el proceso de shell actual ( -p $$ -a -d 0), luego limita la salida solo a PID ( -t), produciendo los PID en ambos lados de la tubería.

Tenga en cuenta que:

  1. Puede haber más de un PID encontrado en el extremo de origen, por ejemplo { echo Hi; sleep 5 ; } | whatpipe.sh, probablemente producirá un bash(la subshell de entrada) y sleep 5.
  2. +Esolo está disponible si lsofse compiló con -DHASUXSOCKEPT. Eso debería ser cierto para la mayoría de las distribuciones de Linux modernas, pero compruebe su instalación de todos modos con:lsof -v 2>&1 | grep HASUXSOCKEPT
Adrian
fuente