bashスクリプトのパイプを含む完全なコマンドラインにアクセスすることは可能ですか?


8

例えばコマンドライン:

test.sh arg1 | grep "xyz"

bashスクリプトtest.shに次のgrepを含む完全なコマンドラインを取得することは可能ですか?


「コマンドライン」が何を意味するのかを明確にできますか?
Bart

スクリプト名とその引数だけでなく、完全な文字列(コマンドライン)を含む特別なドル変数があるかどうか疑問に思っています
hellcode

2
これを行うためのユースケースは何ですか?
クサラナンダ

9
@hellcodeは、そのためのパイプの中にいるかどうかを知る必要はありません。出力がTTYかどうかを確認してください。[ -t 1 ] unix.stackexchange.com/a/401938/70524
muru

回答:


6

一般的にそれを行う方法はありません。

しかし、対話型bashシェルは履歴メカニズムとDEBUGトラップを利用して、環境変数を介して、その一部である完全なコマンドラインを実行するコマンドを「伝える」ことができます。

$ 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

番号

bash(またはシェル)は2つの異なるコマンドをフォークします。

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

test.sh 次のgrepについて知ることができませんでした。

ただし、テストによってパイプの「内側」にいることを知っているかもしれません。 /proc/self/fd/1

test.sh

#!/bin/bash

file /proc/self/fd/1

実行する

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

(編集)パイプに接続しているかどうかを知ることに関するmuruのコメントを参照してください。

あなたはそのためのパイプにいるかどうかを知る必要はありません。出力がTTYかどうかを確認してください。[ -t 1 ] https://unix.stackexchange.com/a/401938/70524


便利ですが、これはLinuxでのみ機能し、他のUnixでは機能しません
Scott Earle

2

を使用 /proc/self/fdすると、パイプラインにいるかどうかと、パイプのIDを確認できます。/proc/\*/fd一致するパイプを探して反復する場合、パイプのもう一方の端のPIDを見つけることができます。PIDを使用/proc/$PID/cmdlineすると、ファイル記述子でプロセスを読み取ったり繰り返したりして、パイプされているものを見つけることができます。

$ 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

また、運が良ければ、パイプライン内のさまざまなコマンドが連続したPIDを取得するため、少し簡単になります。

これを行うためのスクリプトは実際にはありませんが、コンセプトを証明しました。


1

もう1つの方法は$BASH_COMMAND自動変数にアクセスすることですが、本質的に揮発性であり、必要な値を取得することは困難です。

私はあなただけを経由してのみ、それをキャッチすることができると思うevalものように、特別な方法であなたのコマンドラインを呼び出す必要れ、:

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

ここで$BASH_COMMANDeval、文字列のビットまでパージしながら拡張され、結果の文字列がヘルパー$CMD変数に「スナップショット」されます。

小さな例:

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

当然、次のように、たとえば、sh -cまたはbash -cでスクリプトを呼び出しているときにも(実際には優れています)機能します。

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

ここでは、変数をパージしません。


1

ご回答ありがとうございます。私はさまざまなことをテストして、次のテストスクリプトに行きました。

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

コマンドライン履歴は、スクリプトの最上行にシバンがない場合にのみ機能します。これが信頼できる他のシステムでも機能するかどうかはわかりません。

ステータスが成功した場合( "/ dev / pts / 3")、 "readlink"(またはArchemarから提案されたような "ファイル")からの出力を抑制することができませんでした。出力を/ dev / nullまたは変数にパイプすると、誤動作の原因になります。そのため、これはスクリプトのオプションにはなりません。

muruが言及したTTYチェックは簡単で、一部のユースケースではすでに十分である可能性があります。

編集:問題は、スクリプトがパイプ上にあるかどうかを判断するだけでなく、完全なコマンドラインを取得する方法でした。それ以上のシステム構成は必要ないので、私は彼の答えの単純な部分 "fc -nl -0"が好きです。これは100%のソリューションではありませんが、これは私の個人的な使用のためのものであり、したがって十分です。あなたの助けをありがとう他のすべての人。


TTYチェックはstdin:に対しても実行できます[ -t 0 ]。したがって、stdinまたはstdoutのいずれかがTTYでないかどうかを確認し、それに応じて続行できます。
muru

stdoutがパイプかどうかを知りたい場合は、Linuxで使用できますif [ -p /dev/stdout ]; ...readlink /proc/self/fd/..これはBSDでは機能しません)。
モスビー

2
スクリプトにはIMNSHOの作業が必要です。echo -eほぼ確実に望んでいません-e。より多くのテストケースが必要$(...)です。ファイル内にリダイレクトし、内部で呼び出します。ただし、これが良いアイデアであるかどうか検討することをお勧めします。lsttyに出力するかパイプに出力するかに応じて出力を変更するようなプログラムは、使用するのが面倒です。
icarus
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.