将我的bash输出重定向到日志文件

时间:2014-01-04 00:42:26

标签: bash

我现在正在执行一系列bash脚本,我想捕获我在输出文件中运行的命令的值。

我认为通过将我的命令与| tee -a outputfile.txt结合使用是可行的,但这仅针对脚本1捕获。

脚本2..4等不会将输出附加到文件中。

环顾四周,输出也可以这种方式重定向 - 1>&1 - 但我有点困惑,因为有时我看到数字1被2替换。我想这与消息类型有关。但是,由于我不知道这个输出重定向是如何调用的,所以我一直在寻找信息。

有任何帮助吗? 谢谢 安德烈

5 个答案:

答案 0 :(得分:3)

假设您在文件sc1.sh,sc2.sh和sc3.sh中有脚本,并且您想将输出写入名为log.txt的文件,那么您可以执行以下操作:

./sc1.sh >> log.txt
./sc2.sh >> log.txt
./sc3.sh >> log.txt

或者,如果您希望通过编写另一个bash脚本来循环浏览其他bash脚本来自动执行该过程:

#!/bin/bash
n=1
num_scripts=3
while test $n -le $num_scripts
do
    script$n.sh >> log.txt
    n=$[n+1]
done

答案 1 :(得分:3)

基本技术

有多种方法可以实现它:

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(除非你碰巧有一个文件围绕着那个可执行文件......)。在括号内完成的任何变量赋值都不会在括号外看到/可访问。这有时可能是一个显示停止(但并非总是如此)。子壳的增量成本几乎可以忽略不计;它存在,但你可能很难衡量它。

还有长期的方式:

command1 >>outputfile.txt
command2 >>outputfile.txt
command3 >>outputfile.txt
command4 >>outputfile.txt

如果您希望证明您是新手shell程序员,请务必使用此技术。如果您希望被视为更高级的shell程序员,请不要。

请注意,上面的所有命令都只将标准输出重定向到指定文件,将标准错误留给原始目标(通常是终端)。如果要获取标准错误以转到同一文件,只需在标准输出重定向后添加2>&1(意思是,将文件描述符2,标准错误发送到与文件描述符1相同的位置,标准输出)


应用技巧

解决评论中提出的问题。

  

通过使用2>&1 >> $_logfile(根据我的回答),我得到了我需要的东西,但现在我的输出文件中也有了我的回声....有没有办法在屏幕和文件上同时打印它们?

啊,所以你不想要一切都去文件......这使事情变得复杂一些。当然有办法;不一定是直截了当的。在主重定向之前,我可能会使用exec 3>&1;将文件描述符3设置为与1相同的位置(标准输出 - 或使用3>&2,如果我希望回声达到标准错误)。然后我创建了一个函数echoecho() { echo "$*"; echo "$*" >&3; },并且我使用echoecho Whatever来进行回显。当您完成文件描述符3时(如果您不打算退出,系统将关闭它),您可以使用exec 3>&-关闭它。

  

当你引用exec那个应该是我在我创建的单个脚本文件中执行的命令时,我将在周期之间执行吗? (请看下面的答案,看看我是如何进化出来的)。对于其余的建议,我完全失去了你。

没有;我引用了Bash(shell)内置命令exec。它可以用于永久地执行I / O重定向(对于脚本的其余部分),或者用新程序替换当前脚本,如exec ls -l - 这可能是一个不好的例子。

  

我想现在我比起初时更加困惑:)这是否有可能创造一个小例子...所以我能更好地理解它?

评论的缺点是它们难以格式化并且尺寸有限。这些限制也是有益的,但有时候必须延长答案。上述时间到了。

为了便于讨论,我将限制自己使用2个命令而不是问题中的4个命令(但这并没有失去任何一般性)。这些命令将是cmd1cmd2,实际上这些命令是同一个脚本的两个不同名称:

#!/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
$

现在,从answer发布的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包含cmd1cmd2,则输出为:

$ 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

并不清楚变量是否必要,但我们会将其保留到位。请注意,这两个使用' 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

另一种选择是:

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 … >&3echo的标准输出发送到文件描述符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
$

这大致就是我在评论中所说的全部内容。

答案 2 :(得分:1)

subshell中运行命令(通过在一组括号中嵌入所有命令),然后将该子shell的输出传递给tee

(command1; command2; command3) | tee outputfile.txt

答案 3 :(得分:0)

尝试

command >> file

附加到文件。

command > file

每次都清除文件,即从位置1开始写入。

答案 4 :(得分:0)

好的,感谢您的所有贡献和投入。组装几个位,我得到了脚本的工作,因为我需要在文件上生成输出。

简而言之,问题在于如何调用这些脚本;外部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”命令只是为了让我知道我在脚本执行中。

感谢您就此提出进一步的建议。

相关问题