【问题标题】:Bash get the command that is piping into a scriptBash 获取通过管道传输到脚本中的命令
【发布时间】:2019-05-27 03:27:12
【问题描述】:

举个例子:

ls -l | grep -i readme | ./myscript.sh

我想要做的是在myscript.sh 中获取ls -l | grep -i readme 作为字符串变量。所以基本上我试图在myscript.sh 中使用最后一个管道之前获取整个命令。

这可能吗?

【问题讨论】:

  • 不,这是不可能的。你得到的只是一个先进先出;你没有办法知道它的另一面是什么。 (另外,这已经被问过了;给我一点时间来尝试找到重复的......)
  • 作为一种更好地解释是否可以合理预期它可能的方法,请记住,关于管道的任何内容都不是特定于 shell 的,或者需要 成为输入的命令行。如果您有一个 C 程序执行与 shell 设置管道相同的一组 mkfifo()s、dup2()s 和 execve()s,那么您将以相同的方式连接同一组程序,但没有一个字符串描述它曾经被构造过。
  • (在 UNIX 的操作系统级别,程序被传递 C 字符串的 数组;单字符串结构在 shell 内部,被调用的程序永远不会看到)。
  • @vintnes,将它们的输出存储在文件中如何为处理文件的程序提供创建文件的命令文本?因为这就是他们在这里所要求的——能够访问创建输入的命令文本。
  • @CharlesDuffy XY 问题,那么。任何人都不应该处于这个位置。

标签: bash


【解决方案1】:

不,这是不可能的。

在操作系统级别,管道通过mkfifo()dup2()fork()execve() 系统调用实现。 这并不能告诉程序连接到它的标准输入的命令是什么。确实,不能保证一个代表正在使用的程序管道的字符串完全生成标准输入,即使您的标准输入 真的是一个连接到另一个程序的标准输出的 FIFO;可能该管道是由直接调用execve() 和朋友的程序生成的。

最好的解决方法是反转您的流程。

这不是你要求的,但这是你能得到的。

#!/usr/bin/env bash
printf -v cmd_str '%q ' "$@"  # generate a shell command representing our arguments

while IFS= read -r line; do
  printf 'Output from %s: %s\n' "$cmd_str" "$line"
done < <("$@")       # actually run those arguments as a command, and read from it

...然后让您的脚本启动它从中读取输入的内容,而不是在标准输入上接收它们。

...之后,./yourscript ls -l,或./yourscript sh -c 'ls -l | grep -i readme'。 (当然,除非作为示例,否则永远不要使用它;请参阅ParsingLs)。

【讨论】:

  • 这太棒了!但是,我实际上已经使用 getopt 实现了参数化选项。我试图看看我是否能做的是颠倒它的顺序。 pastebin.com/hzbmvk5K 这是我的完整脚本。你会注意到在第 99 行 # todo 是我试图在我的 command 变量中为我的发布请求捕获管道输出的地方。
  • 当我尝试关注该链接时,该链接显示为“不可用”。
  • ...也就是说,如果您只是想捕获管道内容,content=$(cat) 是您将所有标准输入读入单个变量所需的全部内容,而这 真的是 一个 XY 问题。
  • 不,我认为您的初步评估是正确的。我正在尝试捕获正在通过管道传输到脚本的命令,而不是正在通过管道传输的命令的输出。为了获得输出,我正在使用读取。 pastebin.com/qUBzkxii 那是整个脚本的新链接。注意第 99 行
  • 顺便说一句,pastebin 充满了 JavaScript-y 动画广告。请考虑使用gist.github.comix.io 之类的内容。
【解决方案2】:

一般情况下无法做到,但使用bash 中的history 命令也许可以在某种程度上 做到,前提是满足某些条件:

  • history 必须打开。

  • myscript.sh 开始以来,只有一个 shell 一直在运行,或接受新命令,(或失败,运行 myscript.sh) .

  • 由于默认情况下带有前导空格的命令行不会保存到历史记录中,因此 myscript.sh 的调用命令必须没有前导空格;或者必须更改默认值 - 请参阅 Get bash history to remember only the commands run with space prefixed

  • 调用命令需要以&amp; 结尾,因为没有它,新的命令行在myscript.sh 完成之前不会被添加到历史记录中。

脚本需要是bash 脚​​本,(它不适用于/bin/dash),调用shell 需要做一些准备工作。在脚本首先运行之前的某个时间:

shopt -s histappend
PROMPT_COMMAND="history -a; history -n"

...这使得bash 历史可遗传。 (代码来自unutbu's answer to a related question。)

然后 myscript.sh 可能会去:

#!/bin/bash
history -w
printf 'calling command was: %s\n' \
    "$(history | rev | 
       grep "$0" ~/.bash_history | tail -1)"

试运行:

echo googa | ./myscript.sh &

输出,(减去“&amp;”相关的杂物):

calling command was: echo googa | ./myscript.sh &

可以通过将“&amp;”更改为“&amp; fg”来减半,但生成的输出不会包含“fg”后缀。

【讨论】:

  • 这显然非常脆弱。如果您同时运行多个 shell,您需要希望自己足够幸运,能够在正确的时间刷新其历史记录。您还需要希望该命令没有以阻止它显示在历史记录中的方式被调用(使用某些选项,f/e,在前面添加一个空格会做到这一点),并且用户没有unset HISTFILE (当我运行不值得保存的内容时,我经常这样做)。
  • @CharlesDuffy,谢谢。请参阅修订后的答案。在history 被刷新时,它似乎无论如何都会这样做,但也许用history -w 引导脚本不会受到伤害......
  • 点头。尽管我警告说这不是人们应该尝试实际做的一种做法,它一个可行的答案(受实质性但完全公开的警告),并且有这是一个有用的补充。
【解决方案3】:

我认为你应该像这样将它作为一个字符串参数传递

./myscript.sh "$(ls -l | grep -i readme)"

【讨论】:

  • 谢谢。但这不是我想要完成的。命令的输出将通过管道传输到myscript.sh,因此我需要保留 OP 中的语法
  • 你试过我分享的内容了吗? "$(command)" 将扩展表达式并将命令的输出转换为字符串。我认为这正是您想要实现的目标
  • @HernanGarcia,OP 不是要求命令的 输出,而是要求实际命令本身。他们已经有了输出(可以通过阅读标准输入获得)。
【解决方案4】:

我认为有可能,看看这个例子:

#!/bin/bash
result=""
while read line; do
  result=$result"${line}"
done
echo $result

现在使用管道运行此脚本,例如:

ls -l /etc | ./script.sh

希望对你有帮助:)

【讨论】:

  • 该示例如何演示脚本可以访问字符串ls -l /etc,而不是该命令的输出
  • (顺便说一句,你应该总是引用你的扩展;echo "$result",而不是 echo $result;否则,如果名称包含 * 周围有空格,f/e,它会得到替换为附加名称列表)。
猜你喜欢
  • 2014-06-05
  • 2012-02-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-01
  • 2018-01-25
  • 2023-01-26
  • 2018-08-18
相关资源
最近更新 更多