我现在正在执行一系列 bash 脚本,我想捕获我在输出文件中运行的命令的值。
我认为通过将我的命令与我的命令相结合是可能的,| tee -a outputfile.txt
但这仅针对脚本 1 捕获。
脚本 2..4 等不会将输出附加到文件中。
环顾四周,输出也可以通过这种方式重定向 - 1>&1
- 但我有点困惑,因为有时我看到数字 1 被 2 替换。我想这与消息传递的类型有关。但是,由于我不知道这个输出重定向是如何调用的,所以我一直在寻找信息。
有什么帮助吗?谢谢安德里亚
有多种方法可以实现:
exec >outputfile.txt
command1
command2
command3
command4
Run Code Online (Sandbox Code Playgroud)
这会将整个脚本的标准输出更改为日志文件。
我通常首选的方法是:
{
command1
command2
command3
command4
} > outputfile.txt
Run Code Online (Sandbox Code Playgroud)
这会对大括号范围内的所有命令进行 I/O 重定向。小心,你必须同时治疗{
和}
,如果他们的命令; 它们不能出现在任何地方。这不会创建子外壳——这是我喜欢它的主要原因。
您可以用括号替换大括号:
(
command1
command2
command3
command4
) > outputfile.txt
Run Code Online (Sandbox Code Playgroud)
与大括号相比,您可以对括号的放置更加傲慢,因此:
(command1
command2
command3
command4) > outputfile.txt
Run Code Online (Sandbox Code Playgroud)
也可以(但是用大括号这样做,shell 将无法找到命令{command1
(除非你碰巧有一个可执行文件和......)。这会创建一个子 shell。在括号内完成的任何变量赋值不会在括号外被看到/访问。有时(但并非总是如此)这可能是一个阻碍。子外壳的增量成本几乎可以忽略不计;它存在,但你可能很难来测量它。
还有一种长线方法:
command1 >>outputfile.txt
command2 >>outputfile.txt
command3 >>outputfile.txt
command4 >>outputfile.txt
Run Code Online (Sandbox Code Playgroud)
如果你想证明你是一个新手 shell 程序员,一定要使用这个技巧。如果您希望被视为更高级的 shell 程序员,请不要这样做。
请注意,上述所有命令仅将标准输出重定向到命名文件,而将标准错误转到原始目的地(通常是终端)。如果你想让标准错误转到同一个文件,只需在2>&1
标准输出重定向后添加(意思是,将文件描述符2,标准错误,发送到与文件描述符1,标准输出相同的地方)。
解决评论中提出的问题。
通过使用
2>&1 >> $_logfile
(根据我下面的回答),我得到了我需要的东西,但现在我在输出文件中也有我的 echo ... 。有没有办法同时在屏幕和文件上打印它们?
啊,所以你不想把所有东西都放到文件里……这让事情有点复杂。当然有办法;不一定是直截了当的。我可能exec 3>&1;
会3>&2
在主重定向之前将文件描述符 3 设置为与 1 相同的位置(标准输出 - 或者如果我想要标准错误的回显)。然后我会创建一个函数echoecho() { echo "$*"; echo "$*" >&3; }
,我会echoecho Whatever
用来做回声。完成文件描述符 3 后(如果您不打算退出,则系统将关闭它时)您可以使用exec 3>&-
.
当您提到
exec
这应该是我在我创建的单个脚本文件中执行的命令时,我将在循环之间执行,对吗?(看看下面我的回答,看看我是如何改进脚本的)。对于剩下的建议,我完全失去了你。
不; 我指的是 Bash (shell) 内置命令exec
。它可用于永久执行 I/O 重定向(对于脚本的其余部分),或用新程序替换当前脚本,如exec ls -l
- 这可能是一个不好的例子。
我想现在我比刚开始时更困惑:) 是否可以创建一个小例子...这样我可以更好地理解它?
注释的缺点是它们难以格式化且大小有限。这些限制也是好处,但有时必须扩展答案。说的时候到了。
为了讨论起见,我将把自己限制在 2 个命令而不是问题中的 4 个命令(但这不会失去任何一般性)。这些命令将是cmd1
and cmd2
,实际上它们是同一个脚本的两个不同名称:
#!/bin/bash
for i in {01..10}
do
echo "$0: stdout $i - $*"
echo "$0: stderr $i - error message" >&2
done
Run Code Online (Sandbox Code Playgroud)
如您所见,此脚本将消息写入标准输出和标准错误。例如:
$ ./cmd1 trying to work
./cmd1: stdout 1 - trying to work
./cmd1: stderr 1 - error message
./cmd1: stdout 2 - trying to work
./cmd1: stderr 2 - error message
…
./cmd1: stdout 9 - trying to work
./cmd1: stderr 9 - error message
./cmd1: stdout 10 - trying to work
./cmd1: stderr 10 - error message
$
Run Code Online (Sandbox Code Playgroud)
现在,从Andrea Moro发布的答案中我们发现:
#!/bin/bash
_logfile="output.txt"
# Delete output file if exist
if [ -f $_logfile ];
then
rm $_logfile
fi
for file in ./shell/*
do
$file 2>&1 >> $_logfile
done
Run Code Online (Sandbox Code Playgroud)
我不喜欢变量名开头_
;没有必要,我可以看到。这会将错误重定向到标准输出(当前)所在的位置,然后将标准输出重定向到日志文件。因此,如果子目录shell
包含cmd1
and cmd2
,则输出为:
$ bash ex1.sh
./shell/cmd1: stderr 1 - error message
./shell/cmd1: stderr 2 - error message
…
./shell/cmd1: stderr 9 - error message
./shell/cmd1: stderr 10 - error message
./shell/cmd2: stderr 1 - error message
./shell/cmd2: stderr 2 - error message
…
./shell/cmd2: stderr 9 - error message
./shell/cmd2: stderr 10 - error message
$
Run Code Online (Sandbox Code Playgroud)
要同时获取文件的标准输出和标准错误,您可以使用以下方法之一:
2>>$_logfile >>$_logfile
>>$_logfile 2>&1
Run Code Online (Sandbox Code Playgroud)
I/O 重定向通常是从左到右处理的,除了管道控制在|&
处理 I/O 重定向之前标准输出(以及标准错误,如果您使用)的去向。
修改此脚本以生成标准输出的信息以及记录到日志文件,有多种工作方式。我假设shebang线是#!/bin/bash
从这里开始的。
logfile="output.txt"
rm -f $logfile
for file in ./cmd1 ./cmd2
do
$file trying to work >> $logfile 2>&1
done
Run Code Online (Sandbox Code Playgroud)
这将删除存在的日志文件(但比以前更详细)。标准输出和标准错误上的所有内容都进入日志文件。我们也可以这样写:
logfile="output.txt"
{
for file in ./cmd1 ./cmd2
do
$file trying to work
done
} >$logfile 2>&1
Run Code Online (Sandbox Code Playgroud)
或者代码可以使用括号代替大括号,在功能上只有很小的差异,不会对这个脚本产生实质性影响。或者,实际上,在这种情况下,我们可以使用:
logfile="output.txt"
for file in ./cmd1 ./cmd2
do
$file trying to work
done >$logfile 2>&1
Run Code Online (Sandbox Code Playgroud)
并不清楚该变量是否必要,但我们将保留它。请注意,这两个都使用“clobbering”I/O 重定向,因为它们只创建了一次日志文件,这反过来意味着不需要删除它(尽管可能有这样做的原因 - 与事先运行命令的其他用户有关并留下一个不可写的文件,但是无论如何你应该有一个带日期戳的日志文件,所以这毕竟不是问题)。
显然,如果我们想将某些内容回显到原始标准输出以及日志文件中,我们必须做一些不同的事情,因为标准错误和标准输出都将发送到日志文件中。
一种选择是:
logfile="output.txt"
rm -f $logfile
for file in ./cmd1 ./cmd2
do
echo $file $(date +'%Y-%m-%d %H:%M:%S')
$file trying to work >> $logfile 2>&1
done
Run Code Online (Sandbox Code Playgroud)
另一种选择是:
exec 3>&1
logfile="output.txt"
for file in ./cmd1 ./cmd2
do
echo $file $(date +'%Y-%m-%d %H:%M:%S') >&3
$file trying to work
done >$logfile 2>&1
exec 3>&-
Run Code Online (Sandbox Code Playgroud)
现在文件描述符 3 与原始标准输出位于同一位置。在循环内部,标准输出和标准错误都转到日志文件,但echo … >&3
将标准输出发送echo
到文件描述符 3。
如果您希望将相同的回显输出同时发送到重定向的标准输出和原始标准输出,则可以使用:
exec 3>&1
echoecho()
{
echo "$*"
echo "$*" >&3
}
logfile="output.txt"
for file in ./cmd1 ./cmd2
do
echoecho $file $(date +'%Y-%m-%d %H:%M:%S')
$file trying to work
done >$logfile 2>&1
exec 3>&-
Run Code Online (Sandbox Code Playgroud)
输出结果是:
$ bash ex3.sh
./cmd1 2014-01-07 14:57:13
./cmd2 2014-01-07 14:57:13
$ cat output.txt
./cmd1 2014-01-07 14:57:13
./cmd1: stdout 1 - trying to work
./cmd1: stderr 1 - error message
./cmd1: stdout 2 - trying to work
./cmd1: stderr 2 - error message
…
./cmd1: stdout 9 - trying to work
./cmd1: stderr 9 - error message
./cmd1: stdout 10 - trying to work
./cmd1: stderr 10 - error message
./cmd2 2014-01-07 14:57:13
./cmd2: stdout 1 - trying to work
./cmd2: stderr 1 - error message
./cmd2: stdout 2 - trying to work
./cmd2: stderr 2 - error message
…
./cmd2: stdout 9 - trying to work
./cmd2: stderr 9 - error message
./cmd2: stdout 10 - trying to work
./cmd2: stderr 10 - error message
$
Run Code Online (Sandbox Code Playgroud)
这大致就是我在评论中所说的,完整地写出来。