Comment déterminez-vous la commande réelle qui vous pénètre?

9

Disons que j'ai un script bash appelé log.sh. Dans ce script, je veux lire l'entrée d'un tuyau, mais je veux également connaître la commande utilisée pour diriger l'entrée en moi. Exemple:

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

Dans le script shell, je veux connaître la commande tail -f /var/log/httpd/error.

St. John Johnson
la source
Je suis extrêmement curieux de savoir pourquoi vous voulez cela.
Ignacio Vazquez-Abrams le
Je suppose que vous créez un type de programme GUI qui capture et traite les pids?
palbakulich
Je voudrais savoir pour pouvoir distinguer où mettre les résultats. En fonction de la commande et du nom de fichier, je souhaiterais effectuer différentes actions.
St.John Johnson
4
Cela me semble être une violation majeure du principe de la moindre surprise. Si le script doit faire des choses différentes dans des circonstances différentes, cela doit être contrôlé par une option de ligne de commande plutôt que par la provenance de son entrée.
Dave Sherohman
@Dave, je suis d'accord. Disons simplement, pour cet exemple, que je veux juste «savoir» quelle est la commande entrante.
St.John Johnson

Réponses:

7

Akira a suggéré d'utiliser lsof.

Voici comment vous pouvez l'écrire:

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

L'exécuter:

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

Une autre façon consiste à utiliser des groupes de processus.

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

L'exécuter:

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

Notez qu'ils ne fonctionnent tous les deux que si la commande sur le côté gauche du tuyau s'exécute suffisamment longtemps pour la psvoir. Vous avez dit que vous l'utilisiez avec tail -f, donc je doute que ce soit un problème.

$ sleep 0 | ./whatpipe1.sh 

$ sleep 1 | ./whatpipe1.sh
sleep 1
Mikel
la source
au lieu de cet énorme post, j'aurais donné une 2e réponse avec le script basé sur lsof. beau travail pour celui-là.
akira
@akira Merci. Il a fallu quelques tentatives pour le rendre propre et portable. J'ai appris quelques choses sur procfs et lsof en cours de route. Merci pour l'idée.
Mikel
J'ai accepté la vôtre car elle donne une réponse que d'autres personnes peuvent utiliser directement. @Akira, vous avez fait la plupart du travail, désolé, je n'ai pas pu accepter le vôtre aussi.
St.John Johnson
10

le tube apparaîtra comme une entrée dans la liste des descripteurs de fichiers ouverts de votre processus:

 % 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

vous pouvez également utiliser quelque chose comme:

 % 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

donc, que vous avez l'inode du tuyau :) vous pouvez maintenant rechercher tous les autres processus sous /proc/pour ce tuyau. alors vous aurez la commande qui vous redirige:

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

dans cet exemple, catdirigés vers les quartiers sh. en /proc/29889vous pouvez trouver un fichier appelé cmdlinequi vous dit exactement comment s'appelait:

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

les champs de la ligne de commande sont séparés par NUL, donc ça a l'air un peu moche :)

akira
la source
Je ne sais pas quelle réponse accepter. @Akira, vous avez donné la ventilation réelle de la façon de le déterminer, tandis que @Mikel m'a donné un script. Manière de rendre les choses impressionnantes + difficiles.
Johnson
1

Voici une solution compacte utilisant des lsofdistributions Linux modernes sur modernes:

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

Cela répertorie les fichiers de point de terminaison ( +E) de FD 0 sur le processus shell actuel ( -p $$ -a -d 0), puis limite la sortie aux PID uniquement ( -t), produisant les PID des deux côtés du tuyau.

Notez que:

  1. Il peut y avoir plus d'un PID trouvé à l'extrémité source, par exemple { echo Hi; sleep 5 ; } | whatpipe.shproduira probablement un bash(sous-shell d'entrée) et sleep 5.
  2. +En'est disponible que si a lsofété compilé avec -DHASUXSOCKEPT. Cela devrait être vrai pour la plupart des distributions Linux modernes, mais vérifiez quand même votre installation avec:lsof -v 2>&1 | grep HASUXSOCKEPT
Adrian
la source