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

Posted

技术标签:

【中文标题】如何在脚本本身内重定向整个 shell 脚本的输出?【英文标题】:How to redirect output of an entire shell script within the script itself? 【发布时间】:2010-09-23 19:14:10 【问题描述】:

是否可以将 Bourne shell 脚本的所有输出重定向到某个地方,但脚本本身包含 shell 命令?

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

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

# rest of script...

含义:如果脚本以非交互方式运行(例如,cron),则将所有内容的输出保存到文件中。如果从 shell 以交互方式运行,让输出像往常一样转到标准输出。

我想为通常由 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

【问题讨论】:

测试 $TERM 并不是测试交互模式的最佳方法。相反,测试 stdin 是否为 tty (test -t 0)。 换句话说:如果 [ ! -t 0 ];然后执行 >somefile 2>&1;菲 看到这里所有的好处:http://tldp.org/LDP/abs/html/io-redirection.html 基本上是约书亚所说的。 exec > file 将标准输出重定向到特定文件,exec 在您的更新部分,您还应该关闭 FD 3 和 4,如下所示:exec 1>&3 2>&4 3>&- 4>&- Permission denied 在第一行 exec 上。 【参考方案1】:

解决更新后的问题。

#...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 手册的相关章节是Grouping Commands 和I/O Redirection。 POSIX shell 规范的相关部分是Compound Commands 和I/O Redirection。 Bash 有一些额外的符号,但在其他方面类似于 POSIX shell 规范。

【讨论】:

这比保存原始描述符并稍后恢复要清晰得多。 我不得不做一些谷歌搜索来了解这到底在做什么,所以我想分享一下。花括号变成了"block of code",它实际上创建了一个匿名函数。然后可以重定向代码块中的输出所有内容(参见该链接中的示例 3-2)。另请注意,花括号不会启动subshell,但类似的I/O redirects 可以使用括号在子shell中完成。 我比其他解决方案更喜欢这个解决方案。即使是对 I/O 重定向只有最基本了解的人也能理解发生了什么。另外,它更冗长。而且,作为一名 Pythoner,我喜欢冗长。 最好>>。有些人有>的习惯。附加总是比overw***ing更安全和更推荐。有人编写了一个应用程序,它使用标准复制命令将一些数据导出到同一目的地。 你也可以使用 //something 2>&1 | tee outfile同时显示控制台消息和输出到文件【参考方案2】:

通常我们会将其中之一放置在脚本顶部或附近。解析其命令行的脚本会在解析后进行重定向。

将标准输出发送到文件

exec > file

使用标准错误

exec > file                                                                      
exec 2>&1

将 stdout 和 stderr 都附加到文件中

exec >> file
exec 2>&1

作为Jonathan Lefflermentioned in his comment:

exec 有两个独立的工作。第一个是用新程序替换当前正在执行的shell(脚本)。另一个是更改当前 shell 中的 I/O 重定向。这与exec 没有参数不同。

【讨论】:

我说还要在末尾添加 2>&1,这样 stderr 也会被捕获。 :-) 你把这些放在哪里?在脚本的顶部? 使用此解决方案,还必须重置脚本退出的重定向。从这个意义上说,Jonathan Leffler 的下一个答案更“防故障”。 @JohnRed: exec 有两个独立的工作。一种是用另一个命令替换当前脚本,使用相同的过程——您将另一个命令指定为exec 的参数(并且您可以在执行此操作时调整 I/O 重定向)。另一项工作是更改当前 shell 脚本中的 I/O 重定向而不替换它。这种表示法的区别在于没有命令作为exec 的参数。此答案中的符号是“仅 I/O”变体——它仅更改重定向而不替换正在运行的脚本。 (set 命令同样具有多种用途。) exec > >(tee -a "logs/logdata.log") 2>&1 在屏幕上打印日志并将它们写入文件【参考方案3】:

你可以把整个脚本变成这样的函数:

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

或者,您可以在每次运行时将所有内容记录到日志文件中,并且仍然只需执行以下操作即可将其输出到标准输出:

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

【讨论】:

你的意思是 main_function >> /var/log/my_uber_script.log 2>&1 我喜欢在这样的管道中使用 main_function。但在这种情况下,您的脚本不会返回原始返回值。在 bash 情况下,您应该退出,然后使用 'exit $PIPESTATUS[0]'。【参考方案4】:

为了保存原始的标准输出和标准错误,你可以使用:

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

例如,以下代码将“walla1”和“walla2”打印到日志文件(a.txt),“walla3”打印到stdout,“walla4”打印到stderr。

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

【讨论】:

通常情况下,最好使用exec 5&gt;&amp;1exec 6&gt;&amp;2,对输出使用输出重定向符号而不是输入重定向符号。你侥幸逃脱,因为当脚本从终端运行时,标准输入也是可写的,并且标准输出和标准错误都是可读的(或者它是“副”?)的历史怪癖:终端打开读写和相同的open file description 用于所有三个标准 I/O 文件描述符。【参考方案5】:
[ -t <&0 ] || exec >> test.log

【讨论】:

这是做什么的?

以上是关于如何在脚本本身内重定向整个 shell 脚本的输出?的主要内容,如果未能解决你的问题,请参考以下文章

shell脚本应用的基本概念

从 bash 脚本本身将 stdout 的副本重定向到日志文件

shell脚本基础之详解基本脚本的构建

shell脚本中的重定向符号

Shell 脚本学习笔记十:Shell输入输出重定向

Shell脚本编程之Shell输入/输出重定向