如何使用Bash自动将上一个命令的输出捕获到变量中?

148

我想要能够在后续命令中使用最后一个执行的命令的结果。例如:

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

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

最终更新:

Seth的答案很好用。需要注意的几点是:

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

看到你的编辑后,我想删除我的答案。不知道你是否正在寻找任何内置的东西。 - taskinoor
我找不到任何内置的东西。我在想是否可能通过.bashrc来实现它?我认为这将是一个非常方便的功能。 - armandino
很抱歉,你只能将输出重定向到文件或管道中,或者捕获它,否则它无法被保存。 - bandi
4
没有 Shell 和终端的合作,您无法完成此操作,而它们通常不会合作。请参见 Unix Stack Exchange 上的 How do I reuse the last output from the command line?Using text from previous commands' output - Gilles 'SO- stop being evil'
6
命令输出未被捕获的主要原因之一是其大小可以任意增大,一次可能有多兆字节。虽然并非总是这么大,但大量输出会引起问题。 - Jonathan Leffler
显示剩余8条评论
22个回答

96

我不知道有任何自动执行此操作的变量。如果想要除了复制粘贴结果之外做些其他事情,你可以重新运行刚才所做的操作,例如:

vim $(!!)

!! 是指代“上一个命令”的历史扩展。

如果你预期有单个包含空格或其他特殊字符的文件名,可能会阻止正确的参数解析,请将结果加引号(vim "$(!!)")。不加引号将允许同时打开多个文件,只要它们不包含空格或其他shell解析标记。


1
通常在这种情况下,我会复制粘贴,因为你通常只需要命令输出的一部分。我很惊讶你是第一个提到它的人。 - Bruno De Fraine
5
你可以用以下方式用更少的按键完成相同的操作:vim \!!``。 - psmears
1
执行 date "+%N" 然后 echo $(!!) 可以看到与被接受的答案的不同之处。 - Marinos An

76

这是一个非常hacky的解决方案,但在大多数情况下似乎基本可用。在测试期间,我注意到在命令行上获取^C时有时不能很好地工作,尽管我稍微调整了一下以使其表现得更好一些。

这个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

2
@armandino:这当然会导致期望与标准输出终端交互的程序不能按预期工作(more/less),或在 $LAST (emacs) 中存储奇怪的东西。但我认为这大概是你能得到的最好的结果了。唯一的其他选择是使用(类型)脚本将一切都保存到文件中,然后使用 PROMPT_COMMAND 检查自上一个 PROMPT_COMMAND 以来的更改。这将包括你不想要的内容。但我确信你不会找到比这更接近你想要的东西了。 - Seth Robertson
4
稍微深入一点:PROMPT_COMMAND='last="$(cat /tmp/last)";lasterr="$(cat /tmp/lasterr)"; exec >/dev/tty; exec > >(tee /tmp/last); exec 2>/dev/tty; exec 2> >(tee /tmp/lasterr)' 可以同时提供 $last$lasterr - raphink
@cdosborn:默认情况下,man 命令会通过分页器输出内容。正如我之前的评论所说:“期望通过标准输出与终端交互的程序可能无法按预期工作(more/less)”。more 和 less 都是分页器。 - Seth Robertson
我收到了:“没有这样的文件或目录” - Francisco Corrales Morales
@Raphink 我只是想指出一个更正:你的建议应该以 2> >(tee /tmp/lasterr 1>&2) 结尾,因为 tee 的标准输出必须被重定向回标准错误。 - Hugues
这是我的完整版本,它还可以正确处理尾随换行符:: >/tmp/last_stdout 2>/tmp/last_stderr; PROMPT_COMMAND='last_stdout="$(cat /tmp/last_stdout; printf x)"; last_stdout=${last_stdout%x}; last_stderr="$(cat /tmp/last_stderr; printf x)"; last_stderr=${last_stderr%x}; exec >/dev/tty 2>/dev/tty; exec > >(tee /tmp/last_stdout) 2> >(tee /tmp/last_stderr 1>&2)' 然而,它仍然存在一个问题,即 $last_stderr 包含了上一个命令提示符。 - Hugues

19

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/' '{}' ';'

5
当你赋值时,它们并不会被静默修改,只有在你进行“输出”时才会被修改!只需执行echo "${MY_VAR}"就可以看到这一点。 - paxdiablo

14

声明:

  • 这个答案晚了半年 :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动作选择并粘贴先前命令的输出的一部分:https://github.com/bgribble/lw - Bill Gribble
9年后我找到了一个很好的答案。在此期间,tmux发生了一些变化。首先,tmux分配了一个名为“buffer”的默认缓冲区前缀名称,因此上面的tmux showb -b 0将变为tmux showb -b buffer0。但这仍然不起作用,因为最近的缓冲区现在是最高编号的缓冲区,而不是0。所以你需要使用capture-pane -p buffname。但我没有解决的最大问题是,tmux似乎会返回空行直到屏幕末尾,因此如果光标一开始就在屏幕底部,则此解决方案才有效。 - NotTheDr01ds

13

有不止一种方法可以实现这个。其中一种方法是使用v=$(command),它会将命令的输出赋值给v。例如:

v=$(date)
echo $v

你也可以使用反引号。

v=`date`
echo $v

Bash初学者指南中:

当使用旧式反引号形式的替换时,反斜杠保留其字面含义,除非后跟"$"、"`"或"\"。第一个未经过反斜杠处理的反引号终止命令替换。使用"$(COMMAND)"形式时,圆括号之间的所有字符构成该命令,没有任何特殊处理。

编辑:在问题被编辑后,看起来这不是OP所寻找的东西。据我所知,没有像$_一样用于最后一条命令输出的特殊变量。


12

很简单。使用反引号:

var=`find . -name foo.txt`

然后您可以在未来任何时候使用它

echo $var
mv $var /somewhere

9

我认为您可能能够想出一种解决方案,其中包含将您的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

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

alias命令用于为常用命令创建一个自定义名称,以节省时间和键入次数。

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

然后,通过在任意命令后键入“s”,您可以将结果保存到一个名为“it”的shell变量中。
因此,使用示例如下:
$ which python
/usr/bin/python
$ s
$ file $it
/usr/bin/python: symbolic link to `python2.6'

谢谢!这正是我需要实现的grab函数,它将复制上一个命令中的倒数第n行到剪贴板。https://gist.github.com/davidhq/f37ac87bc77f27c5027e - davidhq

6

使用反引号捕获输出:

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

6
我从这里的建议中提取了这个函数:

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


1
好的,严格来说这不是一个答案,因为“自动”部分完全缺失。 - Tilman Vogel
你能解释一下 cat <<<$grab 选项吗? - leoj
1
引用自 man bash:"Here Strings:这是 Here Documents 的一种变体,格式为:<<<word。将展开 word 并提供给命令的标准输入。"因此,变量 $grab 的值被提供给 cat 的标准输入,并且 cat 会将其发送回标准输出。 - Tilman Vogel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接