¿Es posible acceder a la línea de comando completa, incluidas las tuberías en un script bash?

8

Por ejemplo, línea de comando:

test.sh arg1 | grep "xyz"

¿Es posible obtener la línea de comando completa que incluye el siguiente grep en el script bash test.sh?

código infernal
fuente
¿Puedes aclarar qué quieres decir con "línea de comando"?
Bart
Me pregunto si hay una variable especial en dólares que contiene la cadena completa (la línea de comandos), no sólo el nombre del script y sus argumentos
hellcode
2
¿Cuál sería su caso de uso para hacer esto?
Kusalananda
99
@hellcode no necesita saber si está en una tubería para eso. Simplemente verifique si la salida es un TTY. [ -t 1 ] unix.stackexchange.com/a/401938/70524
muru
1
Relacionado en: unix.stackexchange.com/q/485271/117549
Jeff Schaller

Respuestas:

6

No hay forma de hacerlo en general .

Pero un bashshell interactivo puede aprovechar el mecanismo del historial y la DEBUGtrampa para "decir" a los comandos que ejecuta la línea de comandos completa de la que forman parte a través de una variable de entorno:

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

No

bash (o su shell) bifurcará dos comandos distintos.

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

test.sh No podría saber acerca de seguir grep.

Sin embargo, puede saber que está "dentro" de una tubería al probar /proc/self/fd/1

test.sh

#!/bin/bash

file /proc/self/fd/1

que corren como

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

(Editar) vea el comentario de muru sobre saber si está en una tubería.

no necesitas saber si estás en una tubería para eso. Simplemente verifique si la salida es un TTY. [ -t 1 ] https://unix.stackexchange.com/a/401938/70524

Archemar
fuente
Si bien es útil, esto solo funciona en Linux, no en otros Unixes
Scott Earle el
2

Al usar /proc/self/fd, puede ver si está en una tubería, así como una ID para la tubería. Si itera /proc/\*/fdbuscando la tubería coincidente, puede encontrar el PID del otro extremo de la tubería. Con el PID, puede leer /proc/$PID/cmdliney repetir el proceso en sus descriptores de archivo para encontrar en qué se canaliza.

$ 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

Además, si tiene suerte, los diferentes comandos en la tubería obtendrán PID consecutivos que lo harán un poco más fácil.

En realidad no tengo un guión para hacer esto, pero he demostrado el concepto.

Tim Anderson
fuente
1

Otra forma podría ser acceder a la $BASH_COMMANDvariable automática, pero es inherentemente volátil y difícil de atrapar el valor deseado.

Creo que solo puedes atraparlo solo a través de un eval, lo que también implica invocar tus líneas de comando de una manera especial, como en:

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

Aquí $BASH_COMMANDse expande al mismo tiempo que se purga hasta el evalbit de cadena, y la cadena de resultado se "captura" en una $CMDvariable auxiliar .

Pequeño ejemplo:

$ 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, también puede funcionar (en realidad mejor) al invocar scripts por ej. sh -cO bash -c, como en:

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

Aquí sin purgar la variable.

LL3
fuente
1

Gracias por tus respuestas. He probado diferentes cosas y llegué al siguiente script de prueba:

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

Pruebas:

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

El historial de la línea de comandos solo funciona sin un Shebang en la línea superior del script. No sé si esto funcionará de manera confiable y también en otros sistemas.

No pude suprimir la salida de "readlink" (o "archivo" como se sugiere desde Archemar), cuando el estado fue exitoso ("/ dev / pts / 3"). La salida de tubería a / dev / null o a una variable conduciría a un mal funcionamiento. Entonces, esta no sería una opción para mí en un script.

La verificación de TTY que mencionó muru es fácil y tal vez ya sea suficiente para algunos casos de uso.

Editar: Mi crédito es para mosvy, porque la pregunta era cómo obtener la línea de comando completa y no solo para determinar si el script está en curso. Me gusta la parte simple "fc -nl -0" en su respuesta, porque no se necesita más configuración del sistema. No es una solución al 100 por ciento, pero es solo para mi uso personal y, por lo tanto, suficiente. Gracias a todos los demás por vuestra ayuda.

código infernal
fuente
La comprobación de TTY también se puede hacer por la entrada estándar: [ -t 0 ]. Por lo tanto, puede verificar si stdin o stdout no es un TTY y proceder en consecuencia.
muru
Si desea saber si el stdout es una tubería, en Linux puede usarlo if [ -p /dev/stdout ]; ...(así como readlink /proc/self/fd/..esto no funciona en BSD).
mosvy
2
El script necesita trabajar IMNSHO. es echo -ecasi seguro que no quiere el -e. Necesita más casos de prueba, redirigir a un archivo, ser invocado dentro $(...). Sin embargo, le recomiendo que considere si es una buena idea. Los programas como los lsque cambian su salida dependiendo de si están saliendo a un tty o una tubería son molestos de usar.
icarus