是否可以访问完整的命令行,包括bash脚本中的管道?


8

例如命令行:

test.sh arg1 | grep "xyz"

是否有可能在bash脚本test.sh中获得包括以下grep的完整命令行?


您能否阐明“命令行”的含义?
巴特

我只是想知道是否有一个特殊的Dollar变量,它包含完整的字符串(命令行),而不仅仅是脚本名称及其参数
hellcode

2
您这样做的用例是什么?
库沙兰丹

9
@hellcode,您不需要知道您是否在为此工作。只需检查输出是否为TTY。[ -t 1 ] unix.stackexchange.com/a/401938/70524
muru,

Answers:


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(或您的shell)将派生两个不同的命令。

  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

另一种方法可能是通过访问$BASH_COMMAND自动变量,但是它本质上是易变的,很难捕获所需的值。

我认为您只能通过捕获它eval,这还涉及以特殊方式调用命令行,例如:

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

在这里$BASH_COMMAND扩展,同时清除它直到eval字符串的位,因此结果字符串被“快照”到一个辅助$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
$

当然,在通过eg 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.

命令行历史记录仅在脚本顶部没有Shebang的情况下起作用。不知道这是否可以在其他系统上可靠运行。

当状态成功时(“ / dev / pts / 3”),我无法抑制“ readlink”(或“文件”,如Archemar的建议)的输出。将输出管道输出到/ dev / null或变量会导致故障。因此,对于我而言,这不是脚本中的选项。

大师提到的TTY检查很容易,对于某些用例可能已经足够了。

编辑:我的功劳是mosvy,因为问题是如何获取完整的命令行,而不仅仅是确定脚本是否在管道上。我喜欢他的答案中的简单部分“ fc -nl -0”,因为不需要进一步的系统配置。这不是100%的解决方案,但这只是供我个人使用,因此已足够。感谢所有其他人的帮助。


也可以对stdin:进行TTY检查[ -t 0 ]。因此,您可以检查stdin或stdout是否不是TTY,然后进行相应处理。
muru

如果您想知道stdout是否是管道,可以在Linux上使用if [ -p /dev/stdout ]; ...(就像readlink /proc/self/fd/..在BSD上不起作用一样)。
mosvy

2
该脚本需要工作IMNSHO。将echo -e几乎肯定不希望的-e。您需要更多的测试用例,重定向到文件,并在内部调用$(...)。但是,我敦促您考虑这是否是一个好主意。诸如ls根据其输出到tty或管道而更改其输出的程序令人讨厌使用。
icarus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.