如何使用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个回答

5
通过说“我希望能够在后续命令中使用上次执行命令的结果”,我认为——您是指任何命令的结果,而不仅仅是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。

4

通常我会按照其他人的建议去做...不过这里不需要分配任务:

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

2
如果您只想重新运行上一个命令并获取输出,那么一个简单的bash变量就可以解决问题:
LAST=`!!`

那么你可以使用以下命令在输出上运行:

yourCommand $LAST

这将生成一个新进程并重新运行您的命令,然后给出输出。听起来您真正想要的是一个用于命令输出的bash历史文件。这意味着您需要捕获bash发送到终端的输出。您可以编写一些东西来监视所需的/dev或/proc,但那很混乱。您也可以在终端和bash之间创建一个“特殊管道”,其中tee命令位于中间,并将其重定向到输出文件。
但这两种方法都有点hacky。我认为最好的方法是使用terminator,这是一个更现代的终端,具有输出日志记录功能。只需检查日志文件以获取上一个命令的结果即可。类似于上述内容的bash变量可以使此过程变得更加简单。

1
你可以使用 !!: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

这种符号从命令中获取编号参数:在此处查看“${parameter:offset}”的文档:https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html。此外,还可以在此处查看更多示例:http://www.howtogeek.com/howto/44997/how-to-use-bash-history-to-improve-your-command-line-productivity/。 - Alexander Bird

1

我有一个类似的需求,想要将上一个命令的输出作为下一个命令的输入,就像使用 | (管道符) 一样。

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

将其翻译成中文为:

变成像这样 -

$ which gradle |: ls -altr {}

解决方案: 创建了这个自定义管道。非常简单,使用了xargs -命令。
$ alias :='xargs -I{}'

基本上只需为xargs创建一个简写,它就能够像魔术般工作,非常方便。 我只需在.bash_profile文件中添加别名即可。


1

在执行命令并决定要将结果存储在变量中后,以下是一种方法:

$ 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

1
作为现有答案的替代方案:如果您的文件名可能包含空格,可以使用while
find . -name foo.txt | while IFS= read -r var; do
  echo "$var"
done

正如我所写的那样,只有在文件名中包含空格时,这种差异才是相关的。

注意:唯一内置的东西与输出无关,而是与上一个命令的状态有关。


1
可以使用文件描述符的魔力和 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. 设置shell选项shopt -s lastpipe。如果没有此选项,您将丢失文件描述符。

  2. 确保我的stderr也被捕获2>&1

  3. 将输出导入函数,以便可以引用stdin文件描述符。

  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>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg

现在你可以享受输出了!


0

我发现记得将命令的输出导入到特定文件中有点烦人,我的解决方案是在我的.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)

0
这不是严格的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”是你的朋友)


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