Wie bestimmen Sie den tatsächlichen Befehl, der in Sie geleitet wird?

9

Angenommen, ich habe ein Bash-Skript namens log.sh. In diesem Skript möchte ich Eingaben von einer Pipe einlesen, aber ich möchte auch den Befehl kennen, der zum Weiterleiten von Eingaben in mich verwendet wird. Beispiel:

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

Im Shell-Skript möchte ich den Befehl kennen tail -f /var/log/httpd/error.

St. John Johnson
quelle
Ich bin sehr gespannt, warum Sie das wollen.
Ignacio Vazquez-Abrams
Ich vermute, Sie erstellen eine Art GUI-Programm, das Pids erfasst und verarbeitet.
Palbakulich
Ich würde gerne wissen, damit ich unterscheiden kann, wo die Ergebnisse abgelegt werden sollen. Je nach Befehl und Dateiname möchte ich verschiedene Aktionen ausführen.
St. John Johnson
4
Das klingt für mich nach einer schwerwiegenden Verletzung des Prinzips der geringsten Überraschung. Wenn das Skript unter verschiedenen Umständen unterschiedliche Aufgaben ausführen soll, sollte dies über eine Befehlszeilenoption gesteuert werden und nicht über die Herkunft der Eingabe.
Dave Sherohman
@ Dave, ich stimme zu. Sagen wir einfach, für dieses Beispiel möchte ich nur wissen, was der eingehende Befehl ist.
St. John Johnson

Antworten:

7

Akira schlug vor, zu verwenden lsof.

So könnten Sie es schreiben:

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

Ausführen:

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

Eine andere Möglichkeit ist die Verwendung von Prozessgruppen.

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

Ausführen:

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

Beachten Sie, dass beide nur funktionieren, wenn der Befehl auf der linken Seite der Pipe lange genug ausgeführt wird, um ihn pszu sehen. Sie sagten, Sie hätten es verwendet tail -f, daher bezweifle ich, dass dies ein Problem ist.

$ sleep 0 | ./whatpipe1.sh 

$ sleep 1 | ./whatpipe1.sh
sleep 1
Mikel
quelle
Anstelle dieses riesigen Beitrags hätte ich eine zweite Antwort mit dem lsof-basierten Skript gegeben. gute Arbeit dafür.
Akira
@akira Danke. Es dauerte einige Versuche, um es sauber und tragbar zu machen. Ich habe auf dem Weg ein paar Dinge über procfs und lsof gelernt. Danke für die Idee.
Mikel
Ich habe deine akzeptiert, da sie eine Antwort gibt, die andere Leute direkt verwenden können. @Akira, du hast die meiste Arbeit gemacht, sorry, ich konnte deine auch nicht akzeptieren.
St. John Johnson
10

Die Pipe wird als Eintrag in der Liste der offenen Dateideskriptoren Ihres Prozesses angezeigt:

 % 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

Sie könnten auch etwas verwenden wie:

 % 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

Also, als Sie die Inode der Pipe haben :) können Sie jetzt jeden anderen Prozess unter /proc/nach dieser Pipe durchsuchen . Dann haben Sie den Befehl, der an Sie weitergeleitet wird:

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

in diesem Beispiel catan Stationen weitergeleitet sh. In /proc/29889finden Sie eine Datei namens, cmdlinedie Ihnen sagt, wie genau sie aufgerufen wurde:

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

Die Felder der Kommandozeile sind durch NUL getrennt, daher sieht es etwas hässlich aus :)

Akira
quelle
Ich weiß nicht, welche Antwort ich akzeptieren soll. @Akira, Sie haben die tatsächliche Aufschlüsselung angegeben, wie Sie sie bestimmen können, während @Mikel mir ein Skript gegeben hat. Weg, um Dinge großartig + schwierig zu machen.
St. John Johnson
1

Hier ist eine kompakte Lösung, die lsofmoderne Linux-Distributionen verwendet:

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

Dadurch werden die Endpunktdateien ( +E) von FD 0 im aktuellen Shell-Prozess ( -p $$ -a -d 0) aufgelistet und die Ausgabe auf nur PIDs ( -t) beschränkt, wodurch die PIDs auf beiden Seiten der Pipe erhalten werden.

Beachten Sie, dass:

  1. Möglicherweise befindet sich mehr als eine PID am Quellende, z. B. { echo Hi; sleep 5 ; } | whatpipe.shergibt sich wahrscheinlich a bash(die Eingangsunterschale) und sleep 5.
  2. +Eist nur verfügbar, wenn lsofmit kompiliert wurde -DHASUXSOCKEPT. Das sollte für die meisten modernen Linux-Distributionen gelten, aber überprüfen Sie Ihre Installation trotzdem mit:lsof -v 2>&1 | grep HASUXSOCKEPT
Adrian
quelle