我现在正在执行一系列的bash脚本,并希望将我运行的命令的值捕获到输出文件中。
我以为这可以通过将我的命令与| tee -a outputfile.txt
结合使用来实现,但这只适用于脚本1。
脚本2..4等没有将输出附加到文件中。
看了看周围,输出也可以通过这种方式重定向 - 1>&1
- 但有时我看到数字1被数字2取代。我想这与消息类型有关。然而,由于我不知道如何调用此输出重定向,所以我无法找到信息。
需要帮助吗? 谢谢 安德烈亚
我现在正在执行一系列的bash脚本,并希望将我运行的命令的值捕获到输出文件中。
我以为这可以通过将我的命令与| tee -a outputfile.txt
结合使用来实现,但这只适用于脚本1。
脚本2..4等没有将输出附加到文件中。
看了看周围,输出也可以通过这种方式重定向 - 1>&1
- 但有时我看到数字1被数字2取代。我想这与消息类型有关。然而,由于我不知道如何调用此输出重定向,所以我无法找到信息。
需要帮助吗? 谢谢 安德烈亚
有多种方法可用于实现它:
exec >outputfile.txt
command1
command2
command3
command4
{
command1
command2
command3
command4
} > outputfile.txt
这个功能可以对大括号内的所有命令进行I/O重定向。但要小心,必须像对待命令一样处理{
和}
,它们不能出现在任何地方。这不会创建子shell——这也是我喜欢它的主要原因。
你也可以用圆括号代替大括号:
(
command1
command2
command3
command4
) > outputfile.txt
在放置括号方面,你可以更加随意,相比之下花括号就不然了,所以:
(command1
command2
command3
command4) > outputfile.txt
我也会这样工作(但要用大括号,否则shell将无法找到命令{command1
(除非您恰好有一个可执行的文件在周围并...)。 这将创建一个子shell。在括号内完成的任何变量赋值都不会在括号外看到/访问。这有时可能是一个难以解决的问题(但不总是)。 子shell的增量成本基本可以忽略不计;它存在,但您很可能难以衡量。
还有一种冗长的方式:
command1 >>outputfile.txt
command2 >>outputfile.txt
command3 >>outputfile.txt
command4 >>outputfile.txt
为了讨论方便,本文将限制使用两个命令而非四个(但这并不影响其普适性)。这两个命令分别是 cmd1
和 cmd2
,实际上它们是同一个脚本的不同名称:
#!/bin/bash
for i in {01..10}
do
echo "$0: stdout $i - $*"
echo "$0: stderr $i - error message" >&2
done
正如您所看到的,此脚本将消息写入标准输出和标准错误。例如:
$ ./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
$
现在,从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
我不喜欢以_
开头的变量名;我认为没有必要。这会将错误重定向到标准输出的位置(当前位置),然后将标准输出重定向到日志文件。所以,如果子目录shell
包含cmd1
和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
$
要将标准输出和标准错误输出到文件中,您需要使用以下方法之一:
2>>$_logfile >>$_logfile
>>$_logfile 2>&1
通常情况下,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
这个命令会删除日志文件,如果存在的话(但输出信息比之前少了)。所有的标准输出和错误输出都会被记录到日志文件中。我们也可以这样写:
logfile="output.txt"
{
for file in ./cmd1 ./cmd2
do
$file trying to work
done
} >$logfile 2>&1
或者代码可以使用括号代替花括号,功能上只有很小的差异,不会对该脚本产生实质性影响。 或者,在这种情况下,我们可以使用:
logfile="output.txt"
for file in ./cmd1 ./cmd2
do
$file trying to work
done >$logfile 2>&1
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
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>&-
现在文件描述符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>&-
$ 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
$
这大致就是我在评论中所说的内容,完整地写出来。
{ ...; }
符号并不会启动一个子 shell,因此在该结构中对变量所做的更改是针对主 shell 中的变量而非子 shell。 - Jonathan Lefflerexec 3>&1;
将文件描述符 3 设置为与 1(标准输出)相同的位置 - 或者如果我想要回显到标准错误,则使用 3>&2
- 然后再进行主要重定向。然后我会创建一个函数 echoecho() { echo "$*"; echo "$*" >&3; }
,并使用 echoecho Whatever
来进行回显。当你完成文件描述符 3 的使用时(如果你不打算退出,系统将关闭它),你可以使用 exec 3>&-
关闭它。 - Jonathan Lefflerexec
。它可以用于永久进行I/O重定向(对于脚本的其余部分),或者用新程序替换当前脚本,例如exec ls -l .
— 这可能是一个不好的示例。 - Jonathan Leffler./sc1.sh >> log.txt
./sc2.sh >> log.txt
./sc3.sh >> log.txt
或者,如果你喜欢通过编写另一个Bash脚本来自动化该过程:
#!/bin/bash
n=1
num_scripts=3
while test $n -le $num_scripts
do
script$n.sh >> log.txt
n=$[n+1]
done
尝试
command >> file
追加到文件中。
command > file
tee
:
(command1; command2; command3) | tee outputfile.txt
tee
命令可以放在另一行上,就像我的答案中所示。 - Jonathan Lefflercommand1...command3
替换为 script1...script3
;原理完全相同,就像您在自己的解决方案中展示的那样。 - Emerick Rogul好的,感谢您的贡献和建议。通过组合几个部分,我已经让脚本基本按照我的需求工作,并生成了输出文件。
简而言之,问题在于这些脚本的调用方式;一个外部的 Ruby 脚本启动了 shell 脚本,可能每次都创建了不同的 shell 环境。因此,tee 命令可能没有将输出附加到我的文件中(在每行末尾明确声明),因为它已经存在了。我不确定这是否正确。
所以,我的解决方案是
#!/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
然而,正如我所说,我还面临着一个挑战......现在屏幕上完全看不到任何输出,而我很想至少读取我的“echo”命令,以便让我知道脚本执行的进度。
谢谢您在此方面的进一步建议。