Est-il possible d'accéder à la ligne de commande complète, y compris les canaux dans un script bash?

8

Par exemple, ligne de commande:

test.sh arg1 | grep "xyz"

Est-il possible d'obtenir la ligne de commande complète incluant le grep suivant dans le script bash test.sh?

hellcode
la source
pouvez-vous clarifier ce que vous entendez par «ligne de commande»?
Bart
Je me demande simplement s'il existe une variable dollar spéciale qui contient la chaîne complète (la ligne de commande), pas seulement le nom du script et ses arguments
hellcode
2
Quel serait votre cas d'utilisation pour cela?
Kusalananda
9
@hellcode vous n'avez pas besoin de savoir si vous êtes dans le coup pour ça. Vérifiez simplement si la sortie est un ATS. [ -t 1 ] unix.stackexchange.com/a/401938/70524
muru
1
Relating in: unix.stackexchange.com/q/485271/117549
Jeff Schaller

Réponses:

6

Il n'y a aucun moyen de le faire en général .

Mais un bashshell interactif peut tirer parti du mécanisme d'historique et du DEBUGpiège pour "dire" aux commandes qu'il exécute la ligne de commande complète à laquelle ils appartiennent via une variable d'environnement:

$ 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
la source
13

non

bash (ou votre shell) va bifurquer deux commandes distinctes.

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

test.sh ne pouvait pas savoir à propos de grep.

vous pourriez cependant savoir que vous êtes "à l'intérieur" d'un tuyau en testant /proc/self/fd/1

test.sh

#!/bin/bash

file /proc/self/fd/1

qui fonctionnent comme

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

(Modifier) ​​voir le commentaire de muru sur le fait de savoir si vous êtes sur une pipe.

vous n'avez pas besoin de savoir si vous êtes dans le coup pour ça. Vérifiez simplement si la sortie est un ATS. [ -t 1 ] https://unix.stackexchange.com/a/401938/70524

Archemar
la source
Bien qu'utile, cela ne fonctionne que sur Linux - pas sur les autres Unix
Scott Earle
2

En utilisant /proc/self/fd, vous pouvez voir si vous êtes dans un pipeline ainsi qu'un ID pour le tuyau. Si vous parcourez la /proc/\*/fdrecherche du tuyau correspondant, vous pouvez trouver le PID de l'autre extrémité du tuyau. Avec le PID, vous pouvez ensuite lire /proc/$PID/cmdlineet répéter le processus sur ses descripteurs de fichiers pour trouver dans quoi il est canalisé.

$ 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

De plus, si vous êtes chanceux, les différentes commandes du pipeline recevront des PID consécutifs, ce qui le rendra un peu plus facile.

Je n'ai pas de script pour le faire, mais j'ai prouvé le concept.

Tim Anderson
la source
1

Une autre façon pourrait être d'accéder à la $BASH_COMMANDvariable automatique, mais elle est intrinsèquement volatile et difficile à saisir la valeur souhaitée.

Je pense que vous ne pouvez l'attraper que via un eval, ce qui implique également d'invoquer vos lignes de commande d'une manière spéciale, comme dans:

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

Ici, il $BASH_COMMANDest développé tout en le purgeant jusqu'au evalbit de chaîne, et la chaîne résultante est ainsi "instantanée" dans une $CMDvariable d' aide .

Petit exemple:

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

Naturellement, cela peut aussi fonctionner (en fait mieux) tout en invoquant des scripts par exemple sh -cou bash -c, comme dans:

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

Ici sans purger la variable.

LL3
la source
1

Merci pour vos réponses. J'ai testé différentes choses et suis arrivé au script de test suivant:

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.

L'historique de la ligne de commande fonctionne uniquement sans Shebang sur la ligne supérieure du script. Je ne sais pas si cela fonctionnera de manière fiable et sur d'autres systèmes également.

Je n'ai pas pu supprimer la sortie de "readlink" (ou "file" comme suggéré par Archemar), lorsque le statut a réussi ("/ dev / pts / 3"). La sortie de la tuyauterie vers / dev / null ou vers une variable entraînerait un dysfonctionnement. Donc, ce ne serait pas une option pour moi dans un script.

La vérification ATS mentionnée par muru est facile et peut-être déjà suffisante pour certains cas d'utilisation.

Edit: Mon crédit va à mosvy, car la question était de savoir comment obtenir la ligne de commande complète et pas seulement pour déterminer si le script est sur un canal. J'aime la partie simple "fc -nl -0" dans sa réponse, car aucune configuration système supplémentaire n'est nécessaire. Ce n'est pas une solution à 100%, mais c'est juste pour mon usage personnel et donc suffisant. Merci à tous les autres pour votre aide.

hellcode
la source
Le contrôle ATS peut également être fait pour stdin: [ -t 0 ]. Vous pouvez donc vérifier si stdin ou stdout n'est pas un ATS et procéder en conséquence.
muru
Si vous voulez savoir si la sortie standard est un canal, vous pouvez utiliser sur Linux if [ -p /dev/stdout ]; ...(tout comme readlink /proc/self/fd/..cela ne fonctionne pas sur BSD).
mosvy
2
Le script doit fonctionner IMNSHO. le echo -ene veut certainement pas le -e. Vous avez besoin de plus de cas de test, de redirection vers un fichier, d'être invoqué à l'intérieur $(...). Cependant, je vous exhorte à considérer si c'est une bonne idée. Des programmes comme ceux lsqui modifient leur sortie selon qu'ils sortent vers un tty ou un pipe sont ennuyeux à utiliser.
icarus