【问题标题】:Automatically capture output of last command into a variable using Bash?使用 Bash 自动将最后一个命令的输出捕获到变量中?
【发布时间】:2011-08-22 18:30:51
【问题描述】:

我希望能够在后续命令中使用上次执行命令的结果。例如,

$ find . -name foo.txt
./home/user/some/directory/foo.txt

现在假设我希望能够在编辑器中打开文件,或者删除它,或者用它做其他事情,例如

mv <some-variable-that-contains-the-result> /some/new/location

我该怎么做?也许使用一些 bash 变量?

更新:

为了澄清,我不想手动分配东西。我所追求的是内置 bash 变量之类的东西,例如

ls /tmp
cd $_

$_ 保存前一个命令的最后一个参数。我想要类似的东西,但输出的是最后一个命令。

最终更新:

赛斯的回答效果很好。要记住的几件事:

  • 第一次尝试解决方案时不要忘记touch /tmp/x
  • 只有在最后一个命令的退出代码成功时才会存​​储结果

【问题讨论】:

  • 看到您的编辑后,我想删除我的答案。我想知道您是否正在寻找任何内置的东西。
  • 我找不到任何内置的东西。我想知道是否有可能实现它..也许通过.bahsrc?我认为这将是一个非常方便的功能。
  • 恐怕您只能将输出重定向到文件或管道或捕获它,否则将无法保存。
  • 没有shell和终端的配合是不行的,一般是不配合的。另见How do I reuse the last output from the command line?Using text from previous commands' output Unix Stack Exchange
  • 没有捕获命令输出的主要原因之一是因为输出可以任意大 - 一次很多兆字节。当然,并不总是那么大,但大输出会导致问题。

标签: linux bash command-line


【解决方案1】:

我不知道有什么变量可以自动进行。除了复制粘贴结果之外,您还可以重新运行刚才所做的任何事情,例如

vim $(!!)

!! 是历史扩展,意思是“上一个命令”。

如果您希望单个文件名中包含空格或其他字符可能会妨碍正确的参数解析,请引用结果 (vim "$(!!)")。不加引号将允许一次打开多个文件,只要它们不包含空格或其他 shell 解析标记。

【讨论】:

  • 复制粘贴是我在这种情况下通常所做的,也是因为您通常只需要命令输出的一部分。我很惊讶你是第一个提到它的人。
  • 你可以用更少的按键做同样的事情:vim `!!`
  • 要查看与接受的答案的区别,请执行date "+%N",然后执行echo $(!!)
【解决方案2】:

这是一个非常老套的解决方案,但它似乎在某些时候大部分时间都有效。在测试过程中,我注意到在命令行上获取 ^C 时它有时不能很好地工作,尽管我确实对其进行了一些调整以使其表现更好。

这个 hack 只是一个交互模式的 hack,我非常有信心不会向任何人推荐它。后台命令可能会导致比正常情况更少定义的行为。其他答案是以编程方式获得结果的更好方法。


话虽如此,这里是“解决方案”:

PROMPT_COMMAND='LAST="`cat /tmp/x`"; exec >/dev/tty; exec > >(tee /tmp/x)'

设置此 bash 环境变量并根据需要发出命令。 $LAST 通常会有你要找的输出:

startide seth> fortune
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve
startide seth> echo "$LAST"
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve

【讨论】:

  • @armandino:这当然会导致希望在标准输出上与终端交互的程序无法按预期工作(更多/更少)或在 $LAST(emacs)中存储奇怪的东西。但我认为它和你将得到的一样好。唯一的其他选择是使用(类型)脚本将所有内容的副本保存到文件中,然后使用 PROMPT_COMMAND 检查自上次 PROMPT_COMMAND 以来的更改。不过,这将包括您不想要的东西。不过,我很确定你不会找到更接近你想要的东西。
  • 更进一步:PROMPT_COMMAND='last="$(cat /tmp/last)";lasterr="$(cat /tmp/lasterr)"; exec &gt;/dev/tty; exec &gt; &gt;(tee /tmp/last); exec 2&gt;/dev/tty; exec 2&gt; &gt;(tee /tmp/lasterr)' 提供$last$lasterr
  • @cdosborn: man 默认通过寻呼机发送输出。正如我之前的评论所说:“期望与标准输出终端交互的程序无法按预期工作(更多/更少)”。寻呼机越来越少。
  • 我得到:No such file or directory
  • @Raphink 我只是想指出一个更正:您的建议应该以2&gt; &gt;(tee /tmp/lasterr 1&gt;&amp;2) 结尾,因为tee 的标准输出必须重定向回标准错误。
【解决方案3】:

Bash 是一种丑陋的语言。是的,您可以将输出分配给变量

MY_VAR="$(find -name foo.txt)"
echo "$MY_VAR"

但最好还是希望find 只返回一个结果,并且该结果中没有任何“奇数”字符,例如回车符或换行符,因为它们在分配时会被静默修改到 Bash 变量。

但在使用时最好小心正确地引用你的变量!

最好直接对文件进行操作,例如使用find-execdir(查阅手册)。

find -name foo.txt -execdir vim '{}' ';'

find -name foo.txt -execdir rename 's/\.txt$/.xml/' '{}' ';'

【讨论】:

  • 它们在分配时被静默修改,当你回显时它们被修改!你只需要echo "${MY_VAR}"就能看到这是案子。
【解决方案4】:

有不止一种方法可以做到这一点。一种方法是使用v=$(command),它将命令的输出分配给v。例如:

v=$(date)
echo $v

你也可以使用反引号。

v=`date`
echo $v

来自Bash Beginners Guide

当使用旧式反引号形式的替换时,反斜杠保留其字面含义,除非后面跟“$”、“`”或“\”。前面没有反斜杠的第一个反引号终止命令替换。使用“$(COMMAND)”形式时,括号之间的所有字符组成命令;没有人受到特殊对待。

编辑:在问题中进行编辑后,似乎这不是 OP 正在寻找的东西。据我所知,最后一个命令的输出没有像$_这样的特殊变量。

【讨论】:

    【解决方案5】:

    这很容易。使用反引号:

    var=`find . -name foo.txt`
    

    然后你可以在未来的任何时间使用它

    echo $var
    mv $var /somewhere
    

    【讨论】:

      【解决方案6】:

      免责声明:

      • 这个答案迟了半年:D
      • 我是 tmux 的重度用户
      • 你必须在 tmux 中运行你的 shell 才能工作

      在 tmux 中运行交互式 shell 时,您可以轻松访问当前显示在终端上的数据。让我们看一些有趣的命令:

      • tmux capture-pane:这个将显示的数据复制到 tmux 的内部缓冲区之一。它可以复制当前不可见的历史记录,但我们现在对此不感兴趣
      • tmux list-buffers:显示有关捕获缓冲区的信息。最新的编号为 0。
      • tmux show-buffer -b (buffer num):这会在终端上打印给定缓冲区的内容
      • tmux paste-buffer -b (buffer num):这会将给定缓冲区的内容粘贴为输入

      是的,现在这给了我们很多可能性:) 至于我,我设置了一个简单的别名:alias L="tmux capture-pane; tmux showb -b 0 | tail -n 3 | head -n 1",现在每次我需要访问最后一行时,我只需使用$(L) 来获取它。

      这与程序使用的输出流(无论是 stdin 还是 stderr)、打印方法(ncurses 等)和程序的退出代码无关 - 只需要显示数据。

      【讨论】:

      • 嘿,感谢这个 tmux 提示。我一直在寻找与 OP 基本相同的东西,找到了您的评论,并编写了一个 shell 脚本,让您使用您提到的 tmux 命令和 Vim 动作选择并粘贴上一个命令的部分输出:github.com/bgribble/lw
      • 9 年后我遇到的一个很好的答案。与此同时,tmux 发生了一些变化。首先,tmux 分配了一个默认的缓冲区前缀名称“buffer”,因此上面的tmux showb -b 0 将是tmux showb -b buffer0。但这仍然不起作用,因为最近的缓冲区现在是 最高 编号的缓冲区,而不是 0。所以你需要使用 capture-pane -p buffname。但是我还没有解决的最大问题是 tmux 似乎将空白行返回到屏幕的末尾,所以这个解决方案只有在光标首先位于屏幕底部时才有效。
      【解决方案7】:

      我认为您可能能够破解一个解决方案,该解决方案涉及将您的 shell 设置为包含以下内容的脚本:

      #!/bin/sh
      bash | tee /var/log/bash.out.log
      

      然后,如果您设置$PROMPT_COMMAND 输出分隔符,您可以编写一个辅助函数(可能称为_)来获取该日志的最后一块,因此您可以像这样使用它:

      % find lots*of*files
      ...
      % echo "$(_)"
      ... # same output, but doesn't run the command again
      

      【讨论】:

        【解决方案8】:

        您可以在 bash 配置文件中设置以下别名:

        alias s='it=$($(history | tail -2 | head -1 | cut -d" " -f4-))'
        

        然后,通过在任意命令后键入“s”,您可以将结果保存到 shell 变量“it”。

        所以示例用法是:

        $ which python
        /usr/bin/python
        $ s
        $ file $it
        /usr/bin/python: symbolic link to `python2.6'
        

        【讨论】:

        【解决方案9】:

        用反引号捕获输出:

        output=`program arguments`
        echo $output
        emacs $output
        

        【讨论】:

          【解决方案10】:

          我刚刚从这里的建议中提炼出这个bash 函数:

          grab() {     
            grab=$("$@")
            echo $grab
          }
          

          然后,您只需:

          > grab date
          Do 16. Feb 13:05:04 CET 2012
          > echo $grab
          Do 16. Feb 13:05:04 CET 2012
          

          更新:一位匿名用户建议将echo 替换为printf '%s\n',其优点是它不会处理抓取文本中的-e 等选项。因此,如果您期望或遇到此类特殊情况,请考虑此建议。另一种选择是改用cat &lt;&lt;&lt;$grab

          【讨论】:

          • 好的,严格来说这不是一个答案,因为“自动”部分完全丢失了。
          • 您能解释一下cat &lt;&lt;&lt;$grab 选项吗?
          • 引用man bash:“这里的字符串:这里文档的一种变体,格式是:&lt;&lt;&lt;word 这个词被扩展并提供给标准输入上的命令。”因此,$grab 的值被馈送到标准输入上的cat,而cat 只是将其馈送到标准输出。
          【解决方案11】:

          通过说“我希望能够在后续命令中使用最后执行的命令的结果”,我假设 - 您的意思是任何命令的结果,而不仅仅是 find。

          如果是这样 - xargs 就是您要寻找的。

          find . -name foo.txt -print0 | xargs -0 -I{} mv {} /some/new/location/{}

          或者,如果您有兴趣先查看输出:

          find . -name foo.txt -print0

          !! | xargs -0 -I{} mv {} /some/new/location/{}

          即使路径和/或文件名包含空格,此命令也可以处理多个文件,并且像魅力一样工作。

          注意命令的 mv {} /some/new/location/{} 部分。为先前命令打印的每一行构建并执行此命令。在这里,之前命令打印的行被替换为 {}

          xargs 手册页摘录:

          xargs - 从标准输入构建和执行命令行

          更多详细信息请参见手册页: man xargs

          【讨论】:

            【解决方案12】:

            我通常按照这里其他人的建议去做......没有分配:

            $find . -iname '*.cpp' -print
            ./foo.cpp
            ./bar.cpp
            $vi `!!`
            2 files to edit
            

            如果你愿意,你可以变得更漂亮:

            $grep -R "some variable" * | grep -v tags
            ./foo/bar/xxx
            ./bar/foo/yyy
            $vi `!!`
            

            【讨论】:

              【解决方案13】:

              如果您只想重新运行上一个命令并获取输出,则可以使用简单的 bash 变量:

              LAST=`!!`
              

              那么你可以在输出上运行你的命令:

              yourCommand $LAST
              

              这将产生一个新进程并重新运行您的命令,然后为您提供输出。听起来您真正想要的是用于命令输出的 bash 历史文件。这意味着您将需要捕获 bash 发送到终端的输出。您可以编写一些东西来观看必要的 /dev 或 /proc,但这很麻烦。您也可以在您的术语和 bash 之间创建一个“特殊管道”,并在中间使用 tee 命令重定向到您的输出文件。

              但这两种方法都是很老套的解决方案。我认为最好的办法是terminator,这是一个更现代的终端,带有输出日志。只需检查您的日志文件以获取最后一个命令的结果。与上面类似的 bash 变量会使这更简单。

              【讨论】:

                【解决方案14】:

                这是在您执行命令并决定要将结果存储在变量中之后的一种方法:

                $ find . -name foo.txt
                ./home/user/some/directory/foo.txt
                $ OUTPUT=`!!`
                $ echo $OUTPUT
                ./home/user/some/directory/foo.txt
                $ mv $OUTPUT somewhere/else/
                

                或者,如果您提前知道您希望将结果放在变量中,则可以使用反引号:

                $ OUTPUT=`find . -name foo.txt`
                $ echo $OUTPUT
                ./home/user/some/directory/foo.txt
                

                【讨论】:

                  【解决方案15】:

                  作为现有答案的替代方法:如果您的文件名可以包含如下空格,请使用 while

                  find . -name foo.txt | while IFS= read -r var; do
                    echo "$var"
                  done
                  

                  正如我所写,仅当您必须在文件名中包含空格时,差异才有意义。

                  注意:唯一的内置内容不是关于输出,而是关于最后一个命令的状态。

                  【讨论】:

                    【解决方案16】:

                    您可以使用 !!:1。示例:

                    ~]$ ls *.~
                    class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 
                    
                    ~]$ rm !!:1
                    rm class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 
                    
                    
                    ~]$ ls file_to_remove1 file_to_remove2
                    file_to_remove1 file_to_remove2
                    
                    ~]$ rm !!:1
                    rm file_to_remove1
                    
                    ~]$ rm !!:2
                    rm file_to_remove2
                    

                    【讨论】:

                    【解决方案17】:

                    我也有类似的需求,我想将上一个命令的输出用于下一个命令。很像 | (管道)。 例如

                    $ which gradle 
                    /usr/bin/gradle
                    $ ls -alrt /usr/bin/gradle
                    

                    类似于 -

                    $ which gradle |: ls -altr {}
                    

                    解决方案: 创建了这个自定义管道。真的很简单,使用 xargs -

                    $ alias :='xargs -I{}'
                    

                    通过为 xargs 创建一个简写,基本上没什么,它就像魅力一样,非常方便。 我只是在 .bash_profile 文件中添加别名。

                    【讨论】:

                      【解决方案18】:

                      这可以使用文件描述符的魔力和lastpipe shell 选项来完成。

                      必须使用脚本来完成 - “lastpipe”选项在交互模式下不起作用。

                      这是我测试过的脚本:

                      $ cat shorttest.sh 
                      #!/bin/bash
                      shopt -s lastpipe
                      
                      exit_tests() {
                          EXITMSG="$(cat /proc/self/fd/0)"
                      }
                      
                      ls /bloop 2>&1 | exit_tests
                      
                      echo "My output is \"$EXITMSG\""
                      
                      
                      $ bash shorttest.sh 
                      My output is "ls: cannot access '/bloop': No such file or directory"
                      

                      我在这里做的是:

                      1. 设置外壳选项shopt -s lastpipe。不起作用 没有这个,你会丢失文件描述符。

                      2. 确保我的标准错误也被2&gt;&amp;1捕获

                      3. 将输出通过管道传输到函数中,以便标准输入文件描述符可以 参考。

                      4. 通过获取变量的内容来设置变量 /proc/self/fd/0文件描述符,即stdin。

                      我正在使用它来捕获脚本中的错误,因此如果命令出现问题,我可以停止处理脚本并立即退出。

                      shopt -s lastpipe
                      
                      exit_tests() {
                          MYSTUFF="$(cat /proc/self/fd/0)"
                          BADLINE=$BASH_LINENO
                      }
                      
                      error_msg () {
                          echo -e "$0: line $BADLINE\n\t $MYSTUFF"
                          exit 1
                      }
                      
                      ls /bloop 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg
                      

                      通过这种方式,我可以在我想检查的每个命令后面添加2&gt;&amp;1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg

                      现在您可以享受您的输出了!

                      【讨论】:

                        【解决方案19】:

                        这不是严格意义上的 bash 解决方案,但您可以使用带有 sed 的管道来获取先前命令输出的最后一行。

                        首先让我们看看我在文件夹“a”中有什么

                        rasjani@helruo-dhcp022206::~$ find a
                        a
                        a/foo
                        a/bar
                        a/bat
                        a/baz
                        rasjani@helruo-dhcp022206::~$ 
                        

                        然后,您使用 ls 和 cd 的示例将变成 sed 和管道,如下所示:

                        rasjani@helruo-dhcp022206::~$ cd `find a |sed '$!d'`
                        rasjani@helruo-dhcp022206::~/a/baz$ pwd
                        /home/rasjani/a/baz
                        rasjani@helruo-dhcp022206::~/a/baz$
                        

                        因此,真正的魔法发生在 sed 中,您将任何命令的输出通过管道传输到 sed 中,并且 sed 打印最后一行,您可以将其用作带有反引号的参数。或者你也可以将它与 xargs 结合起来。 (shell 中的“man xargs”是你的朋友)

                        【讨论】:

                          【解决方案20】:

                          shell 没有类似 perl 的特殊符号来存储最后一个命令的回显结果。

                          学习在 awk 中使用管道符号。

                          find . | awk '{ print "FILE:" $0 }'
                          

                          在上面的例子中你可以这样做:

                          find . -name "foo.txt" | awk '{ print "mv "$0" ~/bar/" | "sh" }'
                          

                          【讨论】:

                            【解决方案21】:
                            find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/
                            

                            是另一种方式,虽然很脏。

                            【讨论】:

                            • 找到 . -name foo.txt 1> tmpfile && mv `cat tmpfile` path/to/some/dir && rm tmpfile
                            【解决方案22】:

                            我发现记住将命令的输出通过管道传输到特定文件有点烦人,我的解决方案是 .bash_profile 中的一个函数,它可以捕获文件中的输出并在需要时返回结果。

                            这个的优点是您不必重新运行整个命令(当使用 find 或其他可能很关键的长时间运行的命令时)

                            很简单,把这个粘贴到你的.bash_profile

                            脚本

                            # catch stdin, pipe it to stdout and save to a file
                            catch () { cat - | tee /tmp/catch.out}
                            # print whatever output was saved to a file
                            res () { cat /tmp/catch.out }
                            

                            用法

                            $ find . -name 'filename' | catch
                            /path/to/filename
                            
                            $ res
                            /path/to/filename
                            

                            此时,我倾向于将| catch 添加到所有命令的末尾,因为这样做没有任何成本,而且我不必重新运行需要很长时间才能完成的命令。

                            另外,如果你想在文本编辑器中打开文件输出,你可以这样做:

                            # vim or whatever your favorite text editor is
                            $ vim <(res)
                            

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 2020-03-22
                              • 2014-08-08
                              • 2010-10-11
                              • 2011-10-13
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多