从 Perl 脚本启动 pdf 查看器

Start a pdf viewer from a Perl script

我必须从 Perl 脚本启动 pdf 查看器。观者应 与父进程和父进程 运行 来自的终端分离。如果我关闭父级或终端 查看器仍应保持 运行ning。我考虑了三种方法(使用 evince 作为 pdf 查看器命令):

  1. 使用 systemsh:

    system 'evince test.pdf &';
    
  2. 使用fork():

    $SIG{CHLD} = "IGNORE"; #reap children as they complete
    my $pid = fork();
    if ( $pid == 0 ) {
        exec 'evince', 'test.pdf';
    }
    
  3. 使用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。