Ist es möglich, in einem Bash-Skript auf die gesamte Befehlszeile einschließlich Pipes zuzugreifen?

8

ZB Kommandozeile:

test.sh arg1 | grep "xyz"

Ist es möglich, die vollständige Befehlszeile einschließlich des folgenden grep im Bash-Skript test.sh abzurufen?

Hellcode
quelle
Können Sie klarstellen, was Sie unter "Befehlszeile" verstehen?
Bart
Ich frage mich nur, ob es eine spezielle
Dollarvariable
2
Was wäre Ihr Anwendungsfall dafür?
Kusalananda
9
@hellcode Sie müssen nicht wissen, ob Sie dafür in einer Pipe sind. Überprüfen Sie einfach, ob die Ausgabe ein TTY ist. [ -t 1 ] unix.stackexchange.com/a/401938/70524
muru
1
Bezogen auf: unix.stackexchange.com/q/485271/117549
Jeff Schaller

Antworten:

6

Es gibt im Allgemeinen keine Möglichkeit, dies zu tun .

Eine interaktive bashShell kann jedoch den Verlaufsmechanismus und die DEBUGFalle nutzen, um den Befehlen, über die sie ausgeführt wird, über eine Umgebungsvariable die vollständige Befehlszeile mitzuteilen, zu der sie gehört:

$ trap 'export LC=$(fc -nl -0); LC=${LC#? }' DEBUG
$ sh -c 'printf "last_command={%s}\n" "$LC"' | cat; true
last_command={sh -c 'printf "last_command={%s}\n" "$LC"' | cat; true}
Mosvy
quelle
13

Nein

bash (oder Ihre Shell) gibt zwei verschiedene Befehle aus.

  1. test.sh arg1
  2. grep "xyz"

test.sh Ich konnte nicht wissen, wie ich grep folge.

Sie könnten jedoch durch Testen wissen, dass Sie sich "in" einem Rohr befinden /proc/self/fd/1

test.sh

#!/bin/bash

file /proc/self/fd/1

welche laufen als

> ./test.sh
/proc/self/fd/1: symbolic link to /dev/pts/0
> ./test.sh | cat
/proc/self/fd/1: broken symbolic link to pipe:[25544239]

(Bearbeiten) Siehe Murus Kommentar darüber, ob Sie sich auf einer Pfeife befinden.

Sie müssen nicht wissen, ob Sie dafür in einer Pfeife sind. Überprüfen Sie einfach, ob die Ausgabe ein TTY ist. [ -t 1 ] https://unix.stackexchange.com/a/401938/70524

Archemar
quelle
Dies ist zwar nützlich, funktioniert aber nur unter Linux - nicht unter anderen Unixen
Scott Earle
2

Mithilfe von /proc/self/fdkönnen Sie sehen, ob Sie sich in einer Pipeline befinden, sowie eine ID für die Pipe. Wenn Sie /proc/\*/fdnach dem passenden Rohr suchen, finden Sie die PID am anderen Ende des Rohrs. Mit der PID können Sie dann /proc/$PID/cmdlineden Vorgang in den Dateideskriptoren lesen und wiederholen, um herauszufinden, in was er geleitet wird.

$ cat | cat | cat &
$ ps
  PID TTY          TIME CMD
 6942 pts/16   00:00:00 cat
 6943 pts/16   00:00:00 cat
 6944 pts/16   00:00:00 cat
 7201 pts/16   00:00:00 ps
20925 pts/16   00:00:00 bash
$ ls -l /proc/6942/fd
lrwx------. 1 tim tim 64 Jul 24 19:59 0 -> /dev/pts/16
l-wx------. 1 tim tim 64 Jul 24 19:59 1 -> 'pipe:[49581130]'
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16
$ ls -l /proc/6943/fd
lr-x------. 1 tim tim 64 Jul 24 19:59 0 -> 'pipe:[49581130]'
l-wx------. 1 tim tim 64 Jul 24 19:59 1 -> 'pipe:[49581132]'
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16
$ ls -l /proc/6944/fd
lr-x------. 1 tim tim 64 Jul 24 19:59 0 -> 'pipe:[49581132]'
lrwx------. 1 tim tim 64 Jul 24 19:59 1 -> /dev/pts/16
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16

Wenn Sie Glück haben, erhalten die verschiedenen Befehle in der Pipeline aufeinanderfolgende PIDs, was die Arbeit etwas erleichtert.

Ich habe eigentlich kein Skript dafür, aber ich habe das Konzept bewiesen.

Tim Anderson
quelle
1

Ein anderer Weg könnte der Zugriff auf die $BASH_COMMANDautomatische Variable sein, aber es ist von Natur aus volatil und schwierig, den gewünschten Wert zu erfassen.

Ich denke, Sie konnten es nur über ein abfangen eval, was auch das Aufrufen Ihrer Befehlszeilen auf besondere Weise beinhaltet, wie in:

CMD="${BASH_COMMAND##* eval }" eval './test.sh arg1 | grep "xyz"'

Hier $BASH_COMMANDwird es erweitert, während es gleichzeitig bis zum evalBit der Zeichenfolge gelöscht wird, und die Ergebniszeichenfolge wird somit in eine $CMDHilfsvariable "aufgenommen" .

Kleines Beispiel:

$ cat test.sh
#!/bin/sh

printf 'you are running %s\n' "$CMD"
sleep 1
echo bye bye
$
$ CMD="${BASH_COMMAND##* eval }" eval './test.sh | { grep -nH "."; }'
(standard input):1:you are running './test.sh | { grep -nH "."; }'
(standard input):2:bye bye
$

Natürlich kann es auch funktionieren (eigentlich besser) beim Aufrufen von Skripten durch zB sh -coder bash -c, wie in:

$
$ CMD="${BASH_COMMAND}" sh -c './test.sh | { grep -nH "."; }'
(standard input):1:you are running CMD="${BASH_COMMAND}" sh -c './test.sh | { grep -nH "."; }'
(standard input):2:bye bye
$

Hier ohne die Variable zu löschen.

LL3
quelle
1

Danke für deine Antworten. Ich habe verschiedene Dinge getestet und bin zu folgendem Testskript gekommen:

test.sh:

hist=`fc -nl -0`
# remove leading and trailing whitespaces
hist="$(echo "${hist}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
echo "Command line from history: '$hist'"

if [ -t 1 ]; then
  echo "Direct output to TTY, no pipe involved."
else
  echo "No TTY, maybe a piped command."
fi

if [ -p /dev/stdout ]; then
  echo "stdout is a pipe."
else
  echo "stdout is not a pipe."
fi

readlink -e /proc/self/fd/1
rst=$?
if [ $rst -eq 0 ]; then
  echo "Readlink test status okay, no pipe involved."
else
  echo "Readlink test status error $rst, maybe a piped command."
fi

Tests:

$ ./test.sh test1
Command line from history: './test.sh test1'
Direct output to TTY, no pipe involved.
stdout is not a pipe.
/dev/pts/3
Readlink test status okay, no pipe involved.

$ ./test.sh test2 | cat
Command line from history: './test.sh test2 | cat'
No TTY, maybe a piped command.
stdout is a pipe.
Readlink test status error 1, maybe a piped command.

$ echo "another command before pipe doesn't matter" | ./test.sh test3
Command line from history: 'echo "another command before pipe doesn't matter" | ./test.sh test3'
Direct output to TTY, no pipe involved.
stdout is not a pipe.
/dev/pts/3
Readlink test status okay, no pipe involved.

Der Befehlszeilenverlauf funktioniert nur ohne Shebang in der obersten Zeile des Skripts. Ich weiß nicht, ob dies zuverlässig und auch auf anderen Systemen funktioniert.

Ich konnte die Ausgabe von "readlink" (oder "file", wie von Archemar vorgeschlagen) nicht unterdrücken, als der Status erfolgreich war ("/ dev / pts / 3"). Die Weiterleitung der Ausgabe an / dev / null oder an eine Variable würde zu Fehlfunktionen führen. Das wäre also keine Option für mich in einem Skript.

Der von muru erwähnte TTY-Check ist einfach und für einige Anwendungsfälle möglicherweise bereits ausreichend.

Bearbeiten: Mein Verdienst geht an mosvy, weil die Frage war, wie man die komplette Befehlszeile erhält und nicht nur, ob sich das Skript in einer Pipe befindet. Ich mag den einfachen Teil "fc -nl -0" in seiner Antwort, da keine weitere Systemkonfiguration erforderlich ist. Es ist keine 100-prozentige Lösung, aber dies ist nur für meinen persönlichen Gebrauch und daher ausreichend. Vielen Dank an alle anderen für Ihre Hilfe.

Hellcode
quelle
Die TTY-Prüfung kann auch für stdin durchgeführt werden : [ -t 0 ]. Sie können also überprüfen, ob stdin oder stdout kein TTY ist, und entsprechend vorgehen.
Muru
Wenn Sie wissen möchten, ob das stdout eine Pipe ist, können Sie es unter Linux verwenden if [ -p /dev/stdout ]; ...(genau wie readlink /proc/self/fd/..dies unter BSD nicht funktioniert).
Mosvy
2
Das Skript benötigt Arbeit IMNSHO. das echo -ewill das mit ziemlicher sicherheit nicht -e. Sie benötigen weitere Testfälle, die in eine Datei umgeleitet und darin aufgerufen werden $(...). Ich möchte Sie jedoch dringend bitten, zu prüfen, ob dies eine gute Idee ist. Programme wie solche, lsdie ihre Ausgabe ändern, je nachdem, ob sie an ein tty oder eine Pipe ausgegeben werden, sind ärgerlich zu verwenden.
icarus