使用 Bash 自动将最后一个命令的输出捕获到变量中?
- 2024-10-12 09:57:00
- admin 原创
- 85
问题描述:
我希望能够在后续命令中使用最后执行的命令的结果。例如,
$ 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
第一次尝试解决方案时不要忘记仅当最后一个命令的退出代码成功时才会存储结果
解决方案 1:
我不知道有任何变量可以自动执行此操作。除了复制粘贴结果之外,您还可以重新运行刚刚执行的操作,例如
vim $(!!)
历史扩展的意思是!!
‘前一个命令’。
如果您预计单个文件名中可能包含空格或其他字符,而这些字符可能会妨碍正确的参数解析,请将结果括起来 ( vim "$(!!)"
)。如果不将其括起来,则可以一次打开多个文件,只要它们不包含空格或其他 shell 解析标记即可。
解决方案 2:
这是一个非常棘手的解决方案,但似乎大多数时候都能正常工作。在测试期间,我注意到在^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
解决方案 3:
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/' '{}' ';'
解决方案 4:
免责声明:
这个答案晚了半年:D
我是 tmux 的重度用户
你必须在 tmux 中运行你的 shell 才能实现此功能
在 tmux 中运行交互式 shell 时,您可以轻松访问终端上当前显示的数据。让我们看一些有趣的命令:
tmux 捕获窗格:它将显示的数据复制到 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 等)和程序的退出代码无关 - 只需显示数据。
解决方案 5:
有多种方法可以做到这一点。一种方法是使用v=$(command)
将命令的输出分配给v
。例如:
v=$(date)
echo $v
您也可以使用反引号。
v=`date`
echo $v
来自Bash 初学者指南,
当使用旧式反引号替换形式时,反斜杠保留其字面含义,除非其后跟有“$”、“`”或“”。第一个前面没有反斜杠的反引号终止命令替换。当使用“$(COMMAND)”形式时,括号之间的所有字符都构成命令;没有一个字符会被特殊处理。
编辑:在问题中编辑之后,似乎这不是 OP 正在寻找的东西。据我所知,没有像$_
上一个命令的输出这样的特殊变量。
解决方案 6:
这很简单。使用反引号:
var=`find . -name foo.txt`
然后你可以在将来的任何时候使用它
echo $var
mv $var /somewhere
解决方案 7:
我认为您也许能够找到一个解决方案,即将您的 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
解决方案 8:
您可以在 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:
使用反引号捕获输出:
output=`program arguments`
echo $output
emacs $output
解决方案 10:
我只是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
',其优点是它不会像
-e抓取的文本那样处理选项。因此,如果您预计或遇到此类特殊情况,请考虑此建议。另一个选择是使用
cat <<<$grab`代替。
解决方案 11:
通过说“我希望能够在后续命令中使用最后执行的命令的结果”,我假设 - 你的意思是任何命令的结果,而不仅仅是查找。
如果是这种情况 - 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
解决方案 12:
我通常会按照这里其他人的建议去做......不需要分配:
$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 `!!`
解决方案 13:
如果您想要的只是重新运行上一个命令并获取输出,那么一个简单的 bash 变量就可以起作用:
LAST=`!!`
因此,您可以使用以下命令在输出上运行命令:
yourCommand $LAST
这将生成一个新进程并重新运行命令,然后为您提供输出。听起来您真正想要的是一个用于命令输出的 bash 历史文件。这意味着您需要捕获 bash 发送到您的终端的输出。您可以编写一些内容来监视必要的 /dev 或 /proc,但这很麻烦。您也可以在您的 term 和 bash 之间创建一个“特殊管道”,并在中间使用 tee 命令重定向到您的输出文件。
但这两种都是黑客的解决方案。我认为最好的办法是使用Terminator,这是一个更现代的终端,具有输出日志记录功能。只需检查日志文件以获取最后一个命令的结果。类似于上面的 bash 变量会使这变得更简单。
解决方案 14:
执行命令并决定将结果存储在变量中后,可以采用以下方法:
$ 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
解决方案 15:
作为现有答案的替代方案:while
如果您的文件名可以包含空格,请使用:
find . -name foo.txt | while IFS= read -r var; do
echo "$var"
done
正如我所写的,只有当您必须在文件名中预期空格时,差异才是重要的。
注意:唯一的内置内容不是关于输出而是关于最后一个命令的状态。
解决方案 16:
您可以使用!!: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
解决方案 17:
我也有类似的需求,我想将上一个命令的输出用于下一个命令。很像 |(管道)。例如
$ which gradle
/usr/bin/gradle
$ ls -alrt /usr/bin/gradle
类似于 -
$ which gradle |: ls -altr {}
解决方案:创建此自定义管道。非常简单,使用 xargs -
$ alias :='xargs -I{}'
基本上没有什么需要为 xargs 创建简写,它就像 charm 一样工作,而且非常方便。我只是在 .bash_profile 文件中添加了别名。
解决方案 18:
可以使用文件描述符和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"
我在这里做的事情是:
设置 shell 选项
shopt -s lastpipe
。如果没有这个选项,它将无法工作,因为您将丢失文件描述符。确保我的 stderr 也能被捕获
2>&1
将输出通过管道传输到函数中,以便可以引用 stdin 文件描述符。
/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
$MYSTUFF"
exit 1
}
ls /bloop 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg
通过这种方式,我可以2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg
在每一个我想要检查的命令后面添加。
现在您可以享受您的输出了!
解决方案 19:
这不是一个严格的 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”是您的好朋友)
解决方案 20:
shell 没有类似 perl 的特殊符号来存储最后一个命令的回显结果。
学习使用 awk 中的管道符号。
find . | awk '{ print "FILE:" $0 }'
在上面的例子中你可以这样做:
find . -name "foo.txt" | awk '{ print "mv "$0" ~/bar/" | "sh" }'
解决方案 21:
find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/
这是另一种方法,尽管比较肮脏。
解决方案 22:
我发现记住将命令的输出传输到特定文件中有点烦人,我的解决方案是我的一个函数,.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)
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件