从 Perl 脚本启动 pdf 查看器
Start a pdf viewer from a Perl script
我必须从 Perl 脚本启动 pdf 查看器。观者应
与父进程和父进程 运行 来自的终端分离。如果我关闭父级或终端
查看器仍应保持 运行ning。我考虑了三种方法(使用 evince
作为 pdf 查看器命令):
使用 system
和 sh
:
system 'evince test.pdf &';
使用fork()
:
$SIG{CHLD} = "IGNORE"; #reap children as they complete
my $pid = fork();
if ( $pid == 0 ) {
exec 'evince', 'test.pdf';
}
使用Proc::Daemon
:
use Proc::Daemon;
my $daemon = Proc::Daemon->new(
work_dir => '/tmp/evince',
child_STDOUT => '>>stdout.txt',
child_STDERR => '>>stderr.txt',
);
my $pid = $daemon->Init();
if ( $pid == 0 ) {
exec 'evince', 'test.pdf';
}
这些方法之间有什么区别?您会推荐哪种方法?
system 'evince test.pdf &';
根据我的经验,这很可能真的是:
system 'evince $pdf_file &';
如果 $pdf_file
是用户输入,那么我们会得到 shell-injection 错误,例如传入 $(rm -rf /)
的 pdf 名称,甚至只是 ;rm -rf /
。如果名称中有 space 怎么办?好吧,如果你引用它,你可以避免所有这些,对吧?
system 'evince "$pdf_file" &';
好吧,不,现在我要做的就是给你一个文件名";rm -rf "/
。如果我的 pdf 名称中有双引号怎么办?您可以使用单引号,但如果文件名中包含单引号,则会出现同样的问题,并且 shell 注入并没有变得更难。你可以想出一个精心设计的 shellify 函数,它正确地引用一个字符串 all 以便 shell 可以取消引用它并返回到原始条目......但这似乎比你的其他选择,都没有这些问题。
$SIG{CHLD} = "IGNORE"; #reap children as they complete
my $pid = fork();
if ( $pid == 0 ) {
exec 'evince', 'test.pdf';
}
设置全局 $SIG{CHLD}
非常简单...除非您需要在其他 children 死亡时处理它们。所以只有你能判断这是否可以接受。而且,根据我的经验,甚至并非总是如此。我被这个咬过 - 虽然很少。我将其与在其他地方使用 AnyEvent 的应用程序混合在一起,并设法破坏了 AE 的子进程处理。 (如果你将它与任何事件系统混合,同样可能适用,我只是碰巧在使用 AE。)
此外,这缺少 stdout 和 stderr 重定向以及 stdin 重定向。这很容易添加 - 在 if 中,在 exec 之前,只需根据需要关闭并重新打开文件句柄,例如:
close STDOUT; open STDOUT, '>', '/dev/null';
close STDERR; open STDERR, '>', '/dev/null';
close STDIN; open STDIN, '<', '/dev/null';
没什么大不了的。但是,Proc::Daemon 确实为您设置了更多的东西,以确保信号不会从一个进程到达另一个进程,在任一方向上。这取决于您需要达到的严重程度。
对于我的大部分目的,我发现#2 就足够了。我只在几个项目中达到了 Proc::Daemon,但那是 a) 我可以完全控制模块安装的地方,b) 它真的很重要。启动 pdf 查看器通常不会出现这种情况。
我不惜一切代价避免#1 - 我在 shell 注入中有一些相当重要的咬伤,现在尽量避免 shell。
我必须从 Perl 脚本启动 pdf 查看器。观者应
与父进程和父进程 运行 来自的终端分离。如果我关闭父级或终端
查看器仍应保持 运行ning。我考虑了三种方法(使用 evince
作为 pdf 查看器命令):
使用
system
和sh
:system 'evince test.pdf &';
使用
fork()
:$SIG{CHLD} = "IGNORE"; #reap children as they complete my $pid = fork(); if ( $pid == 0 ) { exec 'evince', 'test.pdf'; }
使用
Proc::Daemon
:use Proc::Daemon; my $daemon = Proc::Daemon->new( work_dir => '/tmp/evince', child_STDOUT => '>>stdout.txt', child_STDERR => '>>stderr.txt', ); my $pid = $daemon->Init(); if ( $pid == 0 ) { exec 'evince', 'test.pdf'; }
这些方法之间有什么区别?您会推荐哪种方法?
system 'evince test.pdf &';
根据我的经验,这很可能真的是:
system 'evince $pdf_file &';
如果 $pdf_file
是用户输入,那么我们会得到 shell-injection 错误,例如传入 $(rm -rf /)
的 pdf 名称,甚至只是 ;rm -rf /
。如果名称中有 space 怎么办?好吧,如果你引用它,你可以避免所有这些,对吧?
system 'evince "$pdf_file" &';
好吧,不,现在我要做的就是给你一个文件名";rm -rf "/
。如果我的 pdf 名称中有双引号怎么办?您可以使用单引号,但如果文件名中包含单引号,则会出现同样的问题,并且 shell 注入并没有变得更难。你可以想出一个精心设计的 shellify 函数,它正确地引用一个字符串 all 以便 shell 可以取消引用它并返回到原始条目......但这似乎比你的其他选择,都没有这些问题。
$SIG{CHLD} = "IGNORE"; #reap children as they complete
my $pid = fork();
if ( $pid == 0 ) {
exec 'evince', 'test.pdf';
}
设置全局 $SIG{CHLD}
非常简单...除非您需要在其他 children 死亡时处理它们。所以只有你能判断这是否可以接受。而且,根据我的经验,甚至并非总是如此。我被这个咬过 - 虽然很少。我将其与在其他地方使用 AnyEvent 的应用程序混合在一起,并设法破坏了 AE 的子进程处理。 (如果你将它与任何事件系统混合,同样可能适用,我只是碰巧在使用 AE。)
此外,这缺少 stdout 和 stderr 重定向以及 stdin 重定向。这很容易添加 - 在 if 中,在 exec 之前,只需根据需要关闭并重新打开文件句柄,例如:
close STDOUT; open STDOUT, '>', '/dev/null';
close STDERR; open STDERR, '>', '/dev/null';
close STDIN; open STDIN, '<', '/dev/null';
没什么大不了的。但是,Proc::Daemon 确实为您设置了更多的东西,以确保信号不会从一个进程到达另一个进程,在任一方向上。这取决于您需要达到的严重程度。
对于我的大部分目的,我发现#2 就足够了。我只在几个项目中达到了 Proc::Daemon,但那是 a) 我可以完全控制模块安装的地方,b) 它真的很重要。启动 pdf 查看器通常不会出现这种情况。
我不惜一切代价避免#1 - 我在 shell 注入中有一些相当重要的咬伤,现在尽量避免 shell。