如何重新初始化Perl的STDIN/STDOUT/STDERR?

17

我有一个Perl脚本,可以分叉并使自己成为守护进程。它由cron运行,为了不留下僵尸进程,我关闭了STDIN、STDOUT和STDERR:

open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!";
open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!";
if (!fork()) {
  do_some_fork_stuff();
  }

我有一个问题:我想在此时恢复至少STDOUT(恢复其他2个也不错)。 但是我需要使用什么魔法符号来重新打开STDOUT,使其与以前的一样?
如果我是从tty运行程序,我知道我可以使用“/dev/tty”(但我正在从cron运行并且依赖于其他地方的stdout)。 我还阅读过一些技巧,例如使用open SAVEOUT,“>&STDOUT”将STDOUT放在一边,但仅制作此副本并不能解决原始问题。
我想知道是否有类似open STDOUT,“|-”的魔法(我知道这不是它),可以打开应该打开的STDOUT。

2
在编程风格方面:最好使用三个参数的打开方式,而不是两个参数的打开方式。 - Leon Timmermans
1
如果您的进程是由crond启动的,则STDOUT是一个FIFO,crond会监视其中的错误消息并将其发送到您的电子邮件中。如果您的进程从crond分叉,并关闭该文件描述符,则crond不再监视该FIFO,因此无法再次获取它。如果您愿意,您可以自己安排发送邮件。 - geocar
谢谢geocar,这不是我希望得到的答案,但我会接受。请查看下面我对jmanning2k的回复,了解我最终做了什么。 - Josh
3个回答

15

# 文件描述符的拷贝

open(CPERR, ">&STDERR");

# 将标准错误输出重定向到警告文件

open(STDERR, ">>xyz.log") || die "Error stderr: $!";

# 关闭重定向的文件句柄

close(STDERR) || die "Can't close STDERR: $!";

# 恢复标准输出和标准错误

open(STDERR, ">&CPERR") || die "Can't restore stderr: $!";

我希望这对你有用。- Hariprasad AJ

5
如果仍然有用,我想到了两件事:
  1. 您可以在子进程中关闭STDOUT/STDERR/STDIN(即如果(!fork())。这将允许父进程仍然使用它们,因为它们在那里仍然打开。

  2. 我认为你可以使用更简单的close(STDOUT)而不是打开/dev/null。

例如:
if (!fork()) {
    close(STDIN) or die "Can't close STDIN: $!\n";
    close(STDOUT) or die "Can't close STDOUT: $!\n";
    close(STDERR) or die "Can't close STDERR: $!\n";
    do_some_fork_stuff();
}

1
关闭 STDOUT 而不是重新打开的问题在于,如果您打开其他文件,则它们可能会获得 fd 0、1 或 2 - 防止您将来重新打开 STDOUT。 - jmanning2k
请参阅 perl bug when STDIN, STDOUT and STDERR get closed?,该问题导致了 perl bug #23838。它基本上说:“在我看来,不要关闭 STDIN 并保持其关闭状态,重新打开 /dev/null 或 tmpfile 或其他什么东西...” - Peter V. Mørch

4

关闭后,就无法再重新打开它了。

你为什么需要再次使用STDOUT?是为了将消息写入控制台吗?可以使用/dev/console,或者使用Sys::Syslog将其写入syslog。

但实际上,另一个答案是正确的。如果您想要稍后重新打开stdout,则必须保存旧的stdout(克隆为新的fd)。这确实解决了“僵尸”问题,因为您可以将fd 0(和1和2)重定向到/dev/null。


哎呀,有点复杂,但如果能行的话...尝试在cronjob行中使用exec来修复cron等待您的进程的问题。我在草稿中有这个,但可能在发布之前删掉了它。 - jmanning2k
遗憾的是,尽管我们试过了(明天意味着星期一),但“exec”仍然会留下一个僵尸进程,只是在更低的层次上。 - Josh
@jmanning2k,你为什么说“一旦关闭,就无法再打开”但又说“老实说,另一个答案是正确的。如果您想稍后重新打开它,则必须保存旧的stdout(克隆到新fd)。” 你的说法“一旦关闭,就无法再打开”是不正确的。 - jfritz42
1
如果您在没有保存原始内容的情况下重新打开STDOUT到/dev/null,那么您将无法重新打开相同的STDOUT。 STDOUT是一个写句柄,父进程具有匹配的读文件句柄。 如果您关闭自己的端口,则另一端会关闭。 没有办法让另一端重新打开连接。 在将STDOUT重定向为/dev/null之前克隆文件句柄可以保持活动的文件句柄。 将其称为“close”和“reopen”可能是不准确和令人困惑的部分。 - jmanning2k
@jmanning2k 你好,我觉得我上次的评论表达不够清楚。我的意思是你可以保存原始文件,因此你的回答不应该以“一旦关闭,就无法找回”这个陈述开始。 - jfritz42
显示剩余2条评论

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