使用bash自动将最后一个命令的输出捕获到变量中
1. 概述
使用命令时,我们可能希望将一个命令的输出传递给另一个命令。这通常使用管道 来实现,但是在编写脚本时,我们可能更喜欢将输出存储在一个变量中以便我们可以处理它。
在本教程中,我们将了解如何在变量中捕获命令的输出。我们将在特别的基础上研究如何执行此操作,以及如何创建一些更持久的解决方案,以便我们轻松地参考以前命令的输出。
2. 用例
让我们首先查看几个具有不同输出大小的命令。wc 命令输出文件中的字数,这是一个标量值。ls 命令输出一个值列表,然后我们可能希望将其与mv一起使用以将文件移动到某处。
虽然提出的解决方案与这两种用例兼容,但存储单个值或列表之间的区别在于我们如何使用结果变量。
2.1. 根据文件字数更改目录
第一个用例处理存储wc -w命令的输出:
$ wc -w < file_with_5_words
5
在这种情况下,文件file_with_5_words有 5 个单词。 假设我们想要更改到名称对应于该输出的目录:
$ cd 5
2.2. 根据文件夹的内容移动某些文件
第二个用例是将文件列表(扩展名为txt的文件)移动到目录。为此,我们首先需要列出文件:
$ ls *.txt
ax.txt ay.txt
mv命令的语法是:
mv file1 file2 ... targetDir
这意味着该命令同时接受多个文件作为输入。
3. 临时解决方案
这些解决方案不需要创建函数或额外文件。它们非常适合偶尔使用。
默认情况下,shell 不存储有关命令输出的信息。我们可以参考上一条命令的退出代码。我们还可以运行history 命令来查看每个操作的命令行。因此,我们需要以某种方式运行命令来捕获其输出。
3.1. 显式使用变量
我们可以分配一个变量来包含输出到 命令的标准输出:
$ var=$(wc -w < file_with_5_words)
这也可以使用反向引号来实现:
$ var=`wc -w < file_with_5_words`
**旧式的反向引号替换不保留特殊字符的含义,*如$、 \ 或实际的`本身。这些字符需要使用反斜杠转义。语法$(…)*不以任何特殊方式处理任何字符。
通过这两种方法,我们将wc -w的输出存储在名为var的变量中。然后,我们可以通过在命令中添加美元符号前缀来使用该变量:
$ cd $var
第二个用例的解决方案类似,只是变量包含多个值。让我们用echo命令检查一下:
$ var=`ls *.txt`
$ echo $var
ax.txt ay.txt
在使用echo检查变量的内容并看到它有两个文件之后。现在让我们将它与 mv一起使用:
$ mv $var 5
3.2. 如果我们不正确地使用变量怎么办?
Shell 变量没有类型。我们应该表现得好像所有变量都是字符串。因此,我们需要小心地将变量的内容仅用于可以接受它们的命令。
例如,让我们看看如果我们将 ls的输出(它是一个列表)与cd命令一起使用会发生什么:
$ var=$(ls *.txt)
$ echo $var
ax.txt ay.txt
$ cd $var
-bash: cd: too many arguments
在这里,cd命令失败了,因为它只接受一个输入值,而不接受多个用空格分隔的值。
3.3. 使用历史扩展 - !!
尽管 shell 不跟踪命令的输出,但我们可以使用历史扩展来重新运行先前的命令以将其输出分配给变量:
$ ls *.txt
ax.txt ay.txt
$ var=$(!!)
var=$(ls *.txt)
$ echo $var
ax.txt ay.txt
同样,我们可以使用 history输出中的数字来重新运行之前的命令:
$ history
1097 git log
1098 ls *.txt
$ var=$(!1098)
我们应该注意,这种方法可能会产生不希望的结果。例如,如果命令的输出在每次调用时发生变化,那么我们的变量将包含最近一次调用的输出。
4. 长期解决方案
如果我们经常捕获命令输出,我们可能希望在我们的系统上设置一些脚本来帮助我们。
4.1. 使用 Bash 函数
让我们定义一个函数out2var来捕获命令输出并将其放入var中:
out2var() {
var=$("[[email protected]](/cdn_cgi/l/email_protection)")
printf '%s\n'$var
}
这需要命令行通过输入参数*@执行 ,然后将输出存储在var中。当我们想查看输出时,我们有一个printf 调用。 每次调用函数out2var时,都会分配变量var* :
$ out2var wc -w < file_with_5_words
5
$ echo $var
5
$ cd $var
我们可以将此函数放入我们当前的终端,甚至将其添加到我们的*.bashrc*文件中。
4.2. 使用临时文件和环境变量
我们可以实现一个钩子来将每个命令的输出存储到一个文件中,而不是对我们的命令运行一个特殊的包装器。 此解决方案使用环境变量 :
$ PROMPT_COMMAND='touch /tmp/last_command; var="$(cat /tmp/last_command)"; exec >/dev/tty; exec > >(tee /tmp/last_command)'
首先,我们创建临时文件并设置变量名var,稍后我们将调用它来检索输出。然后,它打印位于*/tmp/last_command中的临时文件的内容。在第一个分号之后,我们在当前控制台终端(由/dev/tty*表示)中使用exec 执行命令。最后,在最后一个分号之后,带有管道的tee 命令将标准输出发送到临时文件和屏幕。
使用相对简单,因为在任何给定命令之后,输出都会自动存储:
$ ls *.txt
ax.txt
ay.txt
$ echo $var
ax.txt ay.txt
$ mv $var 5
如果我们想自动化捕获输出的过程,这应该是首选方法。但是,这种方法有两个缺点:
- 我们应该记住,由于过程是自动的,我们不能多次依赖*$var* 。与之前需要显式调用 out2var 不同,这里我们每次执行一个命令时都会替换*$var*的内容。
- 它捕获每个命令的输出,这可能会导致交互式命令(例如vim)出现问题。
要停止输出的自动存储,我们可以运行:
$ unset PROMPT_COMMAND