bash - 自动将上次执行的命令的输出捕获到变量中

时间:2011-05-10 19:44:25

标签: linux bash command-line

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

$ 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
  • 只有在上一个命令的退出代码成功时才会存​​储结果

22 个答案:

答案 0 :(得分:87)

我不知道任何自动执行的变量。要做一些事情,除了复制粘贴结果,你可以重新运行你刚刚做的任何事情,例如

vim $(!!)

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

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

答案 1 :(得分:67)

这是一个真正的hacky解决方案,但它似乎主要在某些时候工作。在测试期间,我注意到在命令行上获取 ^ 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

答案 2 :(得分:20)

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

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

但更好的希望你最难的find只返回一个结果而且那个结果没有任何&#34;奇怪的&#34;其中的字符,如回车符或换行符,因为它们在分配给Bash变量时将被静默修改。

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

最好直接对文件执行操作,例如使用find&#39; s -execdir(参阅手册)。

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

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

答案 3 :(得分:12)

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

v=$(date)
echo $v

你也可以使用反引号。

v=`date`
echo $v

来自Bash Beginners Guide

  

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

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

答案 4 :(得分:9)

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

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

然后,如果你设置$PROMPT_COMMAND输出分隔符,你可以编写一个帮助函数(可能称为_),它可以获取该日志的最后一个块,因此你可以使用它: / p>

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

答案 5 :(得分:9)

这很容易。使用反引号:

var=`find . -name foo.txt`

然后你可以在将来的任何时候使用它

echo $var
mv $var /somewhere

答案 6 :(得分:8)

Disclamers:

  • 这个答案是半年后期:D
  • 我是一个沉重的tmux用户
  • 您必须在tmux中运行shell才能使其正常工作

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

  • tmux capture-pane :这个将显示的数据复制到tmux的一个内部缓冲区。它可以复制当前不可见的历史记录,但我们现在对此不感兴趣
  • tmux list-buffers :显示有关捕获的缓冲区的信息。最新的将具有数字0。
  • tmux show-buffer -b(缓冲区数量):这会打印终端上给定缓冲区的内容
  • 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等)和程序的退出代码无关 - 只需要显示数据。

答案 7 :(得分: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

更新:匿名用户建议将echo替换为printf '%s\n',其优势在于它不会在抓取的文本中处理-e等选项。因此,如果您期望或遇到这样的特点,请考虑这个建议。另一种选择是使用cat <<<$grab代替。

答案 8 :(得分:6)

您可以在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 :(得分:5)

使用反引号捕获输出:

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

答案 10 :(得分:5)

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

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

答案 11 :(得分: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 `!!`

答案 12 :(得分:2)

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

LAST=`!!`

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

yourCommand $LAST

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

但这两种都是一种黑客解决方案。我认为最好的事情是terminator,这是一个更加现代化的终端,具有输出记录功能。只需检查您的日志文件以获取最后一个命令的结果。类似于上面的bash变量会使这更简单。

答案 13 :(得分: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. 设置外壳程序选项shopt -s lastpipe。不起作用 否则,您将丢失文件描述符。

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

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

  5. 通过获取变量的内容来设置变量 /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

现在您可以享受输出了!

答案 14 :(得分:1)

我有类似的需求,我想在下一个命令中使用last命令的输出。很像| (管)。 例如

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

类似于 -

$ which gradle |: ls -altr {}

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

$ alias :='xargs -I{}'

基本上没有为xargs创造一个简短的手,它就像魅力一样,非常方便。 我只是在.bash_profile文件中添加别名。

答案 15 :(得分:1)

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

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

正如我所写,只有在文件名中需要空白时才会产生差异。

NB:唯一内置的东西不是关于输出,而是关于最后一个命令的状态。

答案 16 :(得分: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

答案 17 :(得分: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

答案 18 :(得分: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)

答案 19 :(得分:0)

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

学习使用带有awk的管道符号。

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

在上面的示例中,您可以执行以下操作:

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

答案 20 :(得分:0)

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

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

答案 21 :(得分:0)

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

首先让我们看一下文件夹“a”中的内容

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

然后,你的ls和cd的例子将转向sed&amp;管道成这样的东西:

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”是你的朋友)