Por exemplo, linha de comando:
test.sh arg1 | grep "xyz"
É possível obter a linha de comando completa, incluindo o seguinte grep no script bash test.sh?
[ -t 1 ] unix.stackexchange.com/a/401938/70524
Por exemplo, linha de comando:
test.sh arg1 | grep "xyz"
É possível obter a linha de comando completa, incluindo o seguinte grep no script bash test.sh?
[ -t 1 ] unix.stackexchange.com/a/401938/70524
Respostas:
Não há como fazer isso em geral .
Mas um bashshell interativo pode aproveitar o mecanismo do histórico e a DEBUGarmadilha para "contar" os comandos nos quais executa a linha de comando completa da qual fazem parte por meio de uma variável de 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}
não
O bash (ou seu shell) bifurcará dois comandos distintos.
test.sh arg1grep "xyz"test.sh não sabia sobre seguir grep.
no entanto, você pode saber que está "dentro" de um tubo testando /proc/self/fd/1
test.sh
#!/bin/bash
file /proc/self/fd/1
que funcionam 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) veja o comentário de muru sobre saber se você está em um cano.
você não precisa saber se está afim disso. Basta verificar se a saída é um TTY.
[ -t 1 ]https://unix.stackexchange.com/a/401938/70524
Ao usar /proc/self/fd, você pode ver se está em um pipeline e também um ID para o pipe. Se você /proc/\*/fdprocurar o tubo correspondente, poderá encontrar o PID da outra extremidade do tubo. Com o PID, você pode ler /proc/$PID/cmdlinee repetir o processo em seus descritores de arquivo para descobrir o que é canalizado.
$ 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
Além disso, se você tiver sorte, os diferentes comandos no pipeline receberão PIDs consecutivos, o que facilitará um pouco.
Na verdade, não tenho um script para fazer isso, mas provei o conceito.
Outra maneira pode ser acessando a $BASH_COMMANDvariável automática, mas é inerentemente volátil e difícil de capturar o valor desejado.
Eu acho que você só poderia pegá-lo através de um eval, o que também envolve chamar suas linhas de comando de uma maneira especial, como em:
CMD="${BASH_COMMAND##* eval }" eval './test.sh arg1 | grep "xyz"'
Aqui, ele $BASH_COMMANDé expandido enquanto o expurga até o evalbit da string, e a string do resultado é "capturada instantaneamente" em uma $CMDvariável auxiliar .
Pequeno exemplo:
$ 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, também pode funcionar (na verdade melhor) ao invocar scripts por exemplo sh -cou bash -c, como em:
$
$ 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
$
Aqui sem limpar a variável.
Obrigado por suas respostas. Eu testei coisas diferentes e vim para o seguinte script de teste:
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
Testes:
$ ./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.
O histórico da linha de comando está funcionando apenas sem um Shebang na linha superior do script. Não sei se isso funcionará de maneira confiável e em outros sistemas também.
Não consegui suprimir a saída do "readlink" (ou "arquivo", como sugerido pela Archemar), quando o status foi bem-sucedido ("/ dev / pts / 3"). A saída da tubulação para / dev / null ou para uma variável levaria ao mau funcionamento. Portanto, isso não seria uma opção para mim em um script.
A verificação TTY mencionada por muru é fácil e talvez já seja suficiente para alguns casos de uso.
Edit: Meu crédito vai para mosvy, porque a questão era como obter a linha de comando completa e não apenas para determinar se o script está em um cano. Eu gosto da parte simples "fc -nl -0" em sua resposta, porque nenhuma configuração adicional do sistema é necessária. Não é uma solução 100%, mas é apenas para meu uso pessoal e, portanto, suficiente. Obrigado a todos pela ajuda.
[ -t 0 ]. Portanto, você pode verificar se stdin ou stdout não é um TTY e proceder de acordo.
if [ -p /dev/stdout ]; ...(assim readlink /proc/self/fd/..não funciona no BSD).
echo -equase certamente não quer o -e. Você precisa de mais casos de teste, redirecionando para um arquivo, sendo chamado por dentro $(...). No entanto, exorto-o a considerar se é uma boa ideia. Programas como os lsque alteram sua saída, dependendo se estão sendo enviados para um tty ou um pipe, são irritantes de usar.