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


205

是否可以将Bourne Shell脚本的所有输出重定向到某个位置,但是脚本本身内部包含Shell命令?

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

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

# rest of script...

含义:如果脚本是非交互式运行的(例如cron),则将所有内容保存到文件中。如果从外壳以交互方式运行,则使输出照常进入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(测试-t 0)。
克里斯·杰斯特·杨

2
换句话说:如果[!-t 0]; 然后执行> somefile 2>&1; fi
克里斯·杰斯特·杨

1
有关所有优点,请参见此处:http : //tldp.org/LDP/abs/html/io-redirection.html基本上是约书亚所说的。exec> file将stdout重定向到特定文件,exec <file将file替换为stdin,依此类推。与通常相同,但使用exec(有关更多信息,请参阅man exec)。
Loki

在更新部分中,您还应该关闭exec 1>&3 2>&4 3>&- 4>&-
FD

Answers:


173

解决最新的问题。

#...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规范类似。


3
这比保存原始描述符并在以后还原它们要清楚得多。
史蒂夫·马德森

23
我必须进行一些谷歌搜索才能了解这实际上是在做什么,所以我想分享一下。花括号变成一个“代码块”,实际上创建了一个匿名函数。然后,可以重定向代码块中所有内容的输出(请参见该链接的示例3-2)。还要注意,花括号不会启动子外壳程序,但是类似的I / O重定向 可以通过使用括号的子外壳程序来完成。
克里斯,

3
我比其他人更喜欢这种解决方案。即使只有对I / O重定向最基本的了解的人也可以了解正在发生的事情。另外,它更冗长。而且,作为Pythoner,我喜欢冗长的语言。
约翰·雷德

做得更好>>。有些人有养成习惯>。追加总是比泛滥更安全,更值得推荐。有人编写了一个应用程序,该应用程序使用标准的copy命令将一些数据导出到同一目的地。
neverMind9

该应用程序是ccc.bmw71(.pro)(3C电池监控器小部件,以前称为“电池监控器小部件”)始终将电池历史记录导出到/sdcard/bmw_history.txt。如果已经存在,请猜测,覆盖!这使我不小心丢失了一些电池历史记录。我将限制的天数从30设置为一个很高的数字,这使它无效并将其恢复为30。我想在导入现有备份之前导出当前电池历史记录。然后那件事发生了。
neverMind9

175

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

发送标准输出到文件

exec > file

与stderr

exec > file                                                                      
exec 2>&1

将stdout和stderr都附加到文件

exec >> file
exec 2>&1

正如乔纳森·莱夫勒(Jonathan Leffler) 在评论中提到的那样

exec有两个单独的工作。第一个是用新程序替换当前正在执行的shell(脚本)。另一个是更改当前Shell中的I / O重定向。它的特点是不对进行争论exec


7
我说还要在其末尾加上2>&1,也就是这样stderr也会被捕获。:-)
克里斯·杰斯特·杨

7
你把它们放在哪里?在脚本顶部?
科兰2014年

4
使用此解决方案,还必须重置重定向,以退出脚本。从这个意义上讲,乔纳森·莱弗勒的下一个答案是“失败证明”。
Chuim

6
@JohnRed:exec有两个单独的作业。一种是使用相同的过程将当前脚本替换为另一命令-您将另一条命令指定为参数exec(并且您可以在执行操作时调整I / O重定向)。另一项工作是更改当前shell脚本中的I / O重定向而不替换它。该符号的区别在于没有命令作为参数exec。该答案中的表示法是“仅I / O”变体—它仅更改重定向,而不替换正在运行的脚本。(该set命令同样具有多种用途。)
Jonathan Leffler

18
exec > >(tee -a "logs/logdata.log") 2>&1在屏幕上打印日志并将其写入文件
shriyog

32

您可以使整个脚本具有以下功能:

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

您的意思是main_function >> /var/log/my_uber_script.log 2>&1
Felipe

我喜欢在这样的管道中使用main_function。但是在这种情况下,您的脚本不会返回原始返回值。在bash情况下,您应该退出然后使用“退出$ {PIPESTATUS [0]}”。
rudimeier 2014年

7

要保存原始的stdout和stderr,可以使用:

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

例如,以下代码将“ walla1”和“ walla2”打印到日志文件(a.txt),将“ walla3”打印到标准输出,将“ 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

1
通常,最好使用exec 5>&1exec 6>&2,并使用输出重定向符号而不是输出的输入重定向符号。您之所以无法使用它,是因为从终端运行脚本时,标准输入也是可写的,并且标准输出和标准错误都可以通过历史古怪来读取(或者说是“副词”?):终端可以打开读取和写入以及相同的打开文件描述用于所有三个标准I / O文件描述符。
乔纳森·莱夫勒

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.