如何在脚本内部重定向整个脚本的输出?

281

在Bourne shell脚本中,是否可以将所有输出重定向至某个位置,但保留脚本内部的shell命令?

重定向单个命令的输出很容易,但我想要更像这样的东西:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

意思是:如果脚本以非交互方式运行(例如 cron),请将所有输出保存到文件中。如果从 shell 交互式运行,则像往常一样将输出发送到标准输出(stdout)。
我想为通常由 FreeBSD 周期实用程序运行的脚本执行此操作。它是日常运行的一部分,我通常不想每天在电子邮件中看到它,因此我没有将其发送。但是,如果此特定脚本中的某个内容失败,那对我很重要,我希望能够捕获并发送日常作业中这一部分的输出。
更新:Joshua 的答案是完全正确的,但我还想在整个脚本周围保存和恢复 stdout 和 stderr,可以通过以下方式完成:
# save stdout and stderr to file 
# descriptors 3 and 4, 
# then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4

2
测试 $TERM 是否为交互模式的最佳方法并不是测试。相反,测试 stdin 是否为 tty(test -t 0)。 - C. K. Young
2
换句话说:如果 [ ! -t 0 ]; then exec >somefile 2>&1; fi - C. K. Young
1
请点击此处查看所有内容:http://tldp.org/LDP/abs/html/io-redirection.html 基本上就是 Joshua 所说的。exec > file 将标准输出重定向到特定文件,exec < file 将标准输入替换为文件等。与通常相同,但使用 exec(有关更多详细信息,请参见 man exec)。 - Loki
2
在您的更新部分,您还应该关闭FD 3和4,像这样:exec 1>&3 2>&4 3>&- 4>&- - Gurjeet Singh
在第一行的 exec 上出现了 Permission denied - Vince
6个回答

261

回答已更新的问题。

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

大括号 '{ ... }' 提供了一个I/O重定向单元。这些大括号必须出现在命令可能出现的地方 - 简单地说,在一行的开头或分号后面。(是的,可以更加精确;如果您想计较,请告诉我。

你是对的,你可以通过你展示的重定向来保留原始的stdout和stderr,但通常为了让以后维护脚本的人更容易理解正在发生的事情,将重定向代码限定在上述范围内会更简单。

Bash手册的相关部分是组合命令I/O重定向. POSIX shell规范的相关部分是复合命令I/O重定向. Bash有一些额外的符号,但与POSIX shell规范类似。


5
这比保存原始描述符并稍后恢复它们要清晰得多。 - Steve Madsen
44
我需要通过搜索才能理解这段代码的作用,现在我想分享一下。花括号成为了一个"代码块",实际上创建了一个匿名函数。然后,代码块中的所有输出可以被重定向(参见该链接中的示例3-2)。同时请注意,花括号_不会_启动subshell,但是使用圆括号可以类似地使用subshell进行输入/输出重定向。 - chris
4
我喜欢这个解决方案,比其他的更好。即使是对 I/O 重定向只有最基本理解的人也能明白发生了什么。此外,它更加详细表述。作为一个 Python 程序员,我喜欢详细表述。 - John Red
1
最好使用 >>。有些人习惯使用 >。附加永远比覆盖更安全、更可靠。有人编写了一个应用程序,使用标准复制命令将一些数据导出到相同的目的地。 - neverMind9
7
你也可以使用{ //something } 2>&1 | tee outfile,同时显示控制台信息并将输出保存到文件。 - HomeIsWhereThePcIs
显示剩余8条评论

238
通常我们会在脚本的顶部或附近放置其中一个。解析命令行的脚本会在解析后进行重定向。
将标准输出发送到文件。
exec > file

带有标准错误输出。
exec > file                                                                      
exec 2>&1

将标准输出和标准错误输出追加到文件中

exec >> file
exec 2>&1

正如Jonathan Leffler在他的评论中提到的那样

exec 有两个不同的作用。第一个是用一个新程序替换当前正在执行的 shell(脚本)。另一个是改变当前 shell 的 I/O 重定向。这可以通过没有参数传递给 exec 来区分。


8
我建议在末尾加上2>&1,这样标准错误输出也能被捕获。 :-) - C. K. Young
9
放在哪里?放在脚本顶部吗? - colan
5
使用这种解决方案,还需要重置脚本退出时的重定向。Jonathan Leffler 的下一个回答在这方面更加“故障安全”。 - Chuim
9
@JohnRed:exec有两个不同的功能。一个是使用相同的进程,用另一个命令替换当前脚本——您将另一个命令作为exec的参数指定(并且您可以调整I/O重定向)。另一个功能是在不替换当前shell脚本的情况下更改其I/O重定向。如果没有将命令作为exec参数的话,这种符号表示区别明显。此答案中的符号属于“仅限I/O”的变体——它仅更改重定向而不替换正在运行的脚本。(类似地,set命令也是多功能的。) - Jonathan Leffler
33
exec > >(tee -a "logs/logdata.log") 2>&1命令将日志信息同时输出到屏幕和文件中。 - shriyog
显示剩余10条评论

44

你可以像这样将整个脚本制作成一个函数:

main_function() {
  do_things_here
}

然后在脚本的结尾添加以下内容:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

或者,您可以每次运行将所有内容记录到日志文件中,并通过以下方式将其输出到stdout:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log

1
你是指 main_function >> /var/log/my_uber_script.log 2>&1 吗? - Felipe Alvarez
我喜欢在管道中使用main_function。但在这种情况下,你的脚本没有返回原始返回值。在bash中,你应该使用'exit ${PIPESTATUS[0]}'退出。 - rudimeier

9

要保存原始的标准输出(stdout)和标准错误(stderr),您可以使用:

exec [fd number]<&1 
exec [fd number]<&2

例如,下面的代码将会把 "walla1" 和 "walla2" 打印到日志文件(a.txt)中,把 "walla3" 打印到标准输出流,把 "walla4" 打印到标准错误流。
#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6

3
通常,使用输出重定向符号而不是输入重定向符号来处理输出会更好,例如使用exec 5>&1exec 6>&2。您可以这样做是因为当从终端运行脚本时,标准输入也是可写的,并且由于历史惯例中的一个特殊情况,标准输出和标准错误也是可读的:终端被打开以进行读写,并且相同的打开文件描述符用于所有三个标准I/O文件描述符。 - Jonathan Leffler

4
[ -t <&0 ] || exec >> test.log

2
这是做什么的? - djdomi
[ -t ... ]测试是一个条件,用于检查标准输入是否为终端。语法a || b表示运行a,然后如果a失败,则运行b。因此,只有在标准输入不是终端时才会执行exec;因此,如果脚本以交互方式运行,则将所有输出打印到终端,但如果不是,则打印到文件中。 - tripleee

1

我终于想出了如何做到这一点。我不仅想将输出保存到文件中,还想知道 bash 脚本是否成功运行!

我将 bash 命令包装在一个函数中,然后使用 tee 输出到文件调用函数 main_function。之后,我使用 if [ $? -eq 0 ] 捕获输出。

#! /bin/sh -

main_function() {
 python command.py
}

main_function > >(tee -a "/var/www/logs/output.txt") 2>&1

if [ $? -eq 0 ]
then
    echo 'Success!'
else
    echo 'Failure!'
fi

你最后的条件语句破坏了脚本,强制它始终返回成功。你需要捕获退出代码并将其传递给调用者。另请参阅为什么测试“$?”以查看命令是否成功是一种反模式? - tripleee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接