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


139

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

$ 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初次尝试时不要忘记
  • 仅当最后一个命令的退出代码成功时才存储结果

看到您的修改后,我想删除我的答案。我想知道您是否在寻找内置的东西。
taskinoor 2011年

我找不到内置的任何东西。我想知道是否有可能通过.bahsrc来实现它?我认为这将是一个非常方便的功能。
阿曼迪诺

恐怕您所能做的就是将输出重定向到文件或管道或捕获它,否则将无法保存。
班迪

3
如果没有外壳和终端的配合,您将无法做到这一点,而且它们通常不会配合。另请参阅如何重用命令行的最后输出?以及在Unix Stack Exchange使用先前命令输出的文本
吉尔(Gilles)'所以

6
不能捕获命令输出的主要原因之一是因为输出可以任意大-一次很多兆字节。当然,并非总是那么大,但是大的产出会引起问题。
乔纳森·勒夫勒

Answers:


71

这是一个非常棘手的解决方案,但似乎有时大部分时间都可以使用。在测试过程中,我注意到当^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

1
@armandino:这当然会使期望与标准输出终端交互的程序无法按预期运行(更多/更少),或者将奇怪的东西存储在$ LAST中(emacs)。但是我认为这和您将要获得的一样好。唯一的其他选择是使用(类型)脚本将EVERYTHING的副本保存到文件,然后使用PROMPT_COMMAND检查自上一个PROMPT_COMMAND以来的更改。不过,这将包括您不想要的东西。我很确定您不会找到与您想要的东西更接近的东西。
塞斯·罗伯逊

2
进一步说明: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
ℝaphink

@cdosborn:默认情况下,man通过寻呼机发送输出。正如我之前的评论所言:“期望与标准输出终端进行交互的程序无法按预期运行(更多/更少)”。寻呼机或多或少。
塞斯·罗伯森

我得到:No such file or directory
Francisco Corrales Morales 2016年

@Raphink我只想指出一个更正:您的建议应该以结尾,2> >(tee /tmp/lasterr 1>&2)因为的标准输出tee必须重定向回标准错误。
Hugues

90

我不知道会自动执行此操作的任何变量。除了复制粘贴结果外,还可以执行其他操作,例如,重新运行

vim $(!!)

!!历史扩展在哪里,意思是“先前的命令”。

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


1
在这种情况下,我通常会执行复制粘贴操作,这也是因为您通常只需要命令输出的一部分。我很惊讶您是第一个提到它的人。
布鲁诺·德·弗赖恩

4
您可以使用更少的按键来完成几乎相同的操作:vim `!!`
psmears

要查看所接受答案的不同之处,请执行date "+%N"然后执行echo $(!!)
Marinos

20

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 2011年

13

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

v=$(date)
echo $v

您也可以使用反引号。

v=`date`
echo $v

Bash初学者指南

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

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


11

这很容易。使用反引号:

var=`find . -name foo.txt`

然后您将来可以随时使用

echo $var
mv $var /somewhere

11

免责声明:

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

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

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

是的,这给了我们很多可能性:)就我而言,我设置了一个简单的别名: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
Bill Gribble,

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 s='it=$($(history | tail -2 | head -1 | cut -d" " -f4-))'

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

因此,示例用法为:

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

谢谢!这就是实现我的grab功能所需的功能,该功能将第n条行从最后一条命令复制到剪贴板gist.github.com/davidhq/f37ac87bc77f27c5027e
davidhq

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

更新:建议使用匿名用户替换echoprintf '%s\n'该匿名用户的优点是它不会像-e抓取的文本中那样处理选项。因此,如果您期望或遇到这种特殊情况,请考虑此建议。另一种选择是使用cat <<<$grab


1
好的,严格来说,这不是答案,因为“自动”部分完全缺失了。
Tilman Vogel '02

你能解释一下这个cat <<<$grab选项吗?
leoj 2015年

1
引自man bash:“此处的字符串:此处文档的一种变体,格式为:<<<word扩展单词并将其提供给标准输入的命令。” 因此,将的值$grab反馈到catstdin上,并将cat其反馈回stdout。
Tilman Vogel

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


5

使用反引号捕获输出:

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

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命令创建一个“特殊管道”,该命令可重定向到您的输出文件。

但是,这两种都是骇人听闻的解决方案。我认为最好的事情是终结器,这是一个具有输出日志功能的更现代的终端。只需检查您的日志文件以获取上一条命令的结果。与上述类似的bash变量将使其更简单。


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

您可以使用!!: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}”:gnu.org/software/bash/manual/html_node/…。参见更多示例:howtogeek.com/howto/44997/…–
亚历山大·伯德

1

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

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

像-

$ which gradle |: ls -altr {}

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

$ alias :='xargs -I{}'

通过为xargs创建快捷方式基本上什么也没有,它像魅力一样工作,而且非常方便。我只是在.bash_profile文件中添加别名。


1

可以使用文件描述符的神奇功能和lastpipeshell选项来完成。

必须使用脚本来完成-“ 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解决方案,但是您可以将管道与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会发生真正的魔术,您可以将sever命令的输出输出到sed中,并且sed会打印出最后一行,您可以将其用作带有反勾号的参数。或者,您也可以将其组合到xargs。(shell中的“ man xargs”是您的朋友)


0

该外壳程序没有像perl一样的特殊符号来存储最后一条命令的回显结果。

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

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

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

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

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

是另一种方式,尽管很脏。


找 。-name foo.txt 1> tmpfile && mv cat cat tmpfile` path / to / some / dir && rm tmpfile
Kevin

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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.