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

我希望能够在后续命令中使用最后执行的命令的结果,

$ 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
  • 只有在上一个命令的退出代码成功的情况下,结果才会被存储
124925 次浏览

这很简单,使用反引号:

var=`find . -name foo.txt`

你以后随时都可以用

echo $var
mv $var /somewhere

使用反勾捕获输出:

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

我不知道有什么变量能做到 自然而然地。除了复制粘贴结果之外,您还可以重新运行刚才执行的操作,例如

vim $(!!)

其中 !!是历史展开,意思是“前一个命令”。

如果您希望只有一个文件名,其中包含空格或其他字符,这可能会妨碍正确的参数解析,请引用结果(vim "$(!!)")。只要不包含空格或其他 shell 解析令牌,保留未引号将允许同时打开多个文件。

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

v=$(date)
echo $v

你也可以使用反引号。

v=`date`
echo $v

来自 Bash 初学者指南,

当使用旧式的反引号形式的替换时,反斜杠保留其字面意思,除非后面跟着“ $”、“‘”或“”。第一个没有反斜杠的反勾结束指令替代。当使用“ $(COMMAND)”表单时,括号之间的所有字符组成命令; 没有特殊处理。

编辑: 在问题编辑之后,似乎这不是 OP 所要寻找的东西。据我所知,对于 last 命令的输出,没有像 $_这样的特殊变量。

在执行命令并决定将结果存储在变量中之后,有一种方法可以这样做:

$ 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

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

我通常会按照别人的建议去做,没有任务的情况下:

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

作为现有答案的替代: 如果你的文件名可以包含空格,使用 while,如下所示:

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

正如我所写的,只有在文件名必须是空白的情况下,差异才是相关的。

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

这确实是一个蹩脚的解决方案,但它似乎在某些时候大多数情况下是有效的。在测试过程中,我注意到当在命令行上获得 ^C时,它有时不能很好地工作,尽管我确实对它进行了一些调整,以便表现得更好一些。

这个黑客只是一个交互模式的黑客,我非常自信我不会推荐给任何人。后台命令可能导致比正常情况下更少的定义行为。其他的答案是一种更好的通过编程获得结果的方法。


话虽如此,这里有一个“解决方案”:

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

通过说“我希望能够在后续命令中使用最后一个执行命令的结果”,我猜,你是说任何命令的结果,而不仅仅是发现。

如果是这样的话-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

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

首先看看 A 文件夹里有什么

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

然后,您的 ls 和 cd 示例将转换为 sed & piping 类似于下面的内容:

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 结合起来。(“ man xargs”in shell is your friend)

find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/

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

如果您只想重新运行最后一个命令并获得输出,那么一个简单的 bash 变量就可以了:

LAST=`!!`

这样你就可以在输出上运行你的命令:

yourCommand $LAST

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

但这两种解决方案都有点粗糙。我认为最好的东西将是 终结者,这是一个更现代的终端输出日志。只需检查您的日志文件中最后一个命令的结果。一个类似于上面的 bash 变量可以使这个过程更加简单。

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

学习使用 awk 的管道符号。

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

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

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

我认为您可以找到一个解决方案,其中包括将 shell 设置为包含以下内容的脚本:

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

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

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

您可以在 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'

我只是从这里的建议中提炼出这个 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

更新 : 一个匿名用户建议用 printf '%s\n'代替 echo,它的优点是不会处理抓取文本中的 -e这样的选项。所以,如果你期待或经历这样的特殊情况,考虑这个建议。另一种选择是使用 cat <<<$grab

免责声明:

  • 这个答案是半年后的: D
  • 我是个多功能混音器的重度使用者
  • 你必须运行你的外壳在 tmux 的工作

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

  • Tmux 捕获窗格 : 该窗格将显示的数据复制到 tmux 的内部缓冲区之一。它可以复制当前不可见的历史,但我们现在对此不感兴趣
  • Tmux list-buffer : 这显示了关于捕获的缓冲区的信息。
  • Tmux show-buffer-b (buffer num) : 在终端上打印给定缓冲区的内容
  • Tmux 粘贴-buffer-b (buffer num) : 这将粘贴给定缓冲区的内容作为输入

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

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

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

我也有类似的需求,我希望将 last 命令的输出用于下一个命令。就像烟斗一样。 例句

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

比如..

$ which gradle |: ls -altr {}

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

$ alias :='xargs -I{}'

基本上没有创建一个短手 xargs,它的工作原理像魅力,真的很方便。 我只是在.bash _ profile 文件中添加别名。

这可以通过使用神奇的文件描述符和 lastpipe shell 选项来完成。

它必须通过一个脚本来完成-“ lasttube”选项在交互模式下无法工作。

下面是我测试过的脚本:

$ 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

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

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