È possibile accedere alla riga di comando completa, incluse le pipe in uno script bash?


8

Ad esempio riga di comando:

test.sh arg1 | grep "xyz"

È possibile ottenere la riga di comando completa incluso il seguente grep nello script bash test.sh?


puoi chiarire cosa intendi con "riga di comando"?
Bart,

Mi chiedo solo se esiste una speciale variabile in dollari che contiene la stringa completa (la riga di comando), non solo il nome dello script e i suoi argomenti
hellcode,

2
Quale sarebbe il tuo caso d'uso per farlo?
Kusalananda

9
@hellcode non devi sapere se sei nella pipa per questo. Basta controllare se l'output è un TTY. [ -t 1 ] unix.stackexchange.com/a/401938/70524
muru

Risposte:


6

Non c'è modo di farlo in generale .

Ma una bashshell interattiva può sfruttare il meccanismo della cronologia e la DEBUGtrap per "dire" i comandi di cui esegue l'intera riga di comando di cui fanno parte tramite una variabile di ambiente:

$ 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}

13

no

bash (o la tua shell) eseguirà il fork di due comandi distinti.

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

test.sh non potevo sapere di seguire grep.

potresti comunque sapere di essere "dentro" una pipa testando /proc/self/fd/1

test.sh

#!/bin/bash

file /proc/self/fd/1

che funzionano come

> ./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]

(Modifica) vedi il commento di Muru sul sapere se sei su una pipa.

non hai bisogno di sapere se sei in una pipa per questo. Basta controllare se l'output è un TTY. [ -t 1 ] https://unix.stackexchange.com/a/401938/70524


Sebbene utile, questo funziona solo su Linux - non su altri Unix
Scott Earle

2

Usando /proc/self/fd, puoi vedere se sei in una pipeline e un ID per la pipa. Se si esegue la /proc/\*/fdricerca della pipe corrispondente, è possibile trovare il PID dell'altra estremità della pipe. Con il PID, puoi quindi leggere /proc/$PID/cmdlinee ripetere il processo sui suoi descrittori di file per trovare in cosa viene eseguito il piping.

$ 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

Inoltre, se sei fortunato, i diversi comandi nella pipeline riceveranno PID consecutivi che lo renderanno un po 'più semplice.

In realtà non ho una sceneggiatura per farlo, ma ho dimostrato il concetto.


1

Un altro modo potrebbe essere accedendo alla $BASH_COMMANDvariabile automatica, ma è intrinsecamente volatile e difficile ottenere il valore desiderato.

Penso che potresti prenderlo solo tramite un eval, che implica anche invocare le tue righe di comando in un modo speciale, come in:

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

Qui $BASH_COMMANDviene espanso pur eliminandolo fino al evalbit di stringa e la stringa risultante viene quindi "istantanea" in una $CMDvariabile helper .

Piccolo esempio:

$ 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
$

Naturalmente può anche funzionare (in realtà meglio) mentre invoca gli script per esempio sh -co bash -c, come 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
$

Qui senza eliminare la variabile.


1

Grazie per le tue risposte Ho testato diverse cose e sono arrivato al seguente script di test:

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

test:

$ ./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.

La cronologia della riga di comando funziona solo senza Shebang nella riga superiore dello script. Non so se funzionerà in modo affidabile e anche su altri sistemi.

Non sono riuscito a sopprimere l'output di "readlink" (o "file" come suggerito da Archemar), quando lo stato ha avuto esito positivo ("/ dev / pts / 3"). Il piping dell'output su / dev / null o su una variabile comporterebbe un malfunzionamento. Quindi questa non sarebbe un'opzione per me in una sceneggiatura.

Il controllo TTY che Muru ha menzionato è facile ed è forse già sufficiente per alcuni casi d'uso.

Modifica: il mio merito va a mosvy, perché la domanda era come ottenere la riga di comando completa e non solo per determinare se lo script è su una pipe. Mi piace la semplice parte "fc -nl -0" nella sua risposta, perché non è necessaria alcuna ulteriore configurazione del sistema. Non è una soluzione al 100 percento, ma è solo per uso personale e quindi sufficiente. Grazie a tutti gli altri per il vostro aiuto.


Il controllo TTY può essere fatto anche per stdin: [ -t 0 ]. Quindi puoi verificare se stdin o stdout non sono un TTY e procedere di conseguenza.
muru,

Se vuoi sapere se lo stdout è una pipe, su Linux puoi usare if [ -p /dev/stdout ]; ...(proprio come readlink /proc/self/fd/..questo non funziona su BSD).
mosvy,

2
Lo script deve funzionare IMNSHO. il echo -equasi certamente non vuole il -e. Hai bisogno di più casi di test, reindirizzamento a un file, invocazione all'interno $(...). Tuttavia, ti esorto a considerare se questa è una buona idea. Programmi come i lsquali cambiano il loro output a seconda che siano in uscita su un tty o su una pipe sono fastidiosi da usare.
icarus
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.