【问题标题】:How can I debug exec() problems?如何调试 exec() 问题?
【发布时间】:2012-08-25 07:51:48
【问题描述】:

exec 命令在我的服务器上不起作用,它什么也没做,我已经关闭了安全模式,并验证了所有控制台命令都在工作,我尝试过使用绝对路径。我已经检查了应用程序的权限,并且我需要的所有应用程序都具有执行权限。我不知道还能做什么,这是我尝试过的代码的概要。

echo exec('/usr/bin/whoami');

echo exec('whoami');

exec('whoami 2>&1',$output,$return_val);
if($return_val !== 0) {
    echo 'Error<br>';
    print_r($output);   
}

exec('/usr/bin/whoami 2>&1',$output,$return_val);
if($return_val !== 0) {
    echo 'Error<br>';
    print_r($output);   
}

最后两个代码显示:

Error
Array ( )

我已经联系了服务器服务,他们无法帮助我,他们不知道为什么 exec 命令不起作用。

【问题讨论】:

  • “不工作”是什么意思?任何错误输出?
  • 错误设置display_errors = 1error_report = E_ALL? (请注意,您不应在实时系统上显示错误。如果完成,请禁用它)
  • 错误日志没有显示任何关于该问题的信息。我激活了display_errorserror_reporting。仍然找不到任何关于该问题的信息。

标签: php


【解决方案1】:

您可以检索exec 命令的输出和返回代码,这些可能包含可以解释问题的信息...

exec('my command', $output, $return);

【讨论】:

  • 刚刚将代码添加到问题中并没有注意到这个答案,它只是显示了这个。 Error Array ( )
  • $return_val 变量包含什么?
【解决方案2】:

看看/etc/php.ini,在下面:

; This directive allows you to disable certain functions for security reasons.
; It receives a comma-delimited list of function names. This directive is
; *NOT* affected by whether Safe Mode is turned On or Off.
; http://www.php.net/manual/en/ini.sect.safe-mode.php#ini.disable-functions
disable_functions =

确保 exec 没有像这样列出:

disable_functions=exec

如果是这样,请将其删除并重新启动 apache。

为了方便调试,我通常喜欢手动执行 php 文件(无需在主 ini 中设置即可请求更多错误)。为此添加标题:

#!/usr/bin/php
ini_set("display_errors", 1);
ini_set("track_errors", 1);
ini_set("html_errors", 1);
error_reporting(E_ALL);

到文件的开头,使用chmod +x myscript.php 授予它权限并执行它./myscript.php。尤其是在一个繁忙的服务器上,它会非常小心地向日志文件写入很多内容。

编辑

听起来像是权限问题。创建一个简单的 bash 脚本,如 echo "helo world" 并尝试运行它。确保您对文件和包含该文件的文件夹具有权限。你应该只做chmod 755 只是为了测试。

【讨论】:

  • disable_functions 在我的php.ini 中为空,我按照您的建议运行脚本,控制台没有显示任何内容。
  • 非常感谢,成功了,问题是不知何故(因为我的客户可以访问服务器并且他认为他是专家 XD),他复制并更改了应用程序的权限.我已将它们移回原始文件夹并再次配置路径,现在一切正常,我在测试应用程序的权限时发现了这一点。谢谢。
  • @Necroside 他更改了whoami的权限??
  • 嗯,这是一个很好的问题,我刚刚发现了一些权限和路径问题,并将其报告给服务器,他们很快就解决了这个问题,会问,如果票现在还没有关闭。我从服务器得到的答案是我们中的一个人(我们都使用相同的帐户)更改了路径并复制了一些应用程序,所以我要求他们将它们移动并复制回它们所属的位置,我只是将路径改回并更改我需要的应用程序的权限。我不是 linux/unix 专家。
  • 不,whoami 没有改变,它只是被移动了,我不知道它是如何工作的,但它位于 php 找不到的不同路径中。我能够从控制台运行它。
【解决方案3】:

由于您从 PHP 上下文中退出到本机 shell,因此在调试时会遇到很多问题。

我过去使用过的最好和最简单的方法是将脚本的输出写入日志文件并在 PHP 执行期间对其进行跟踪。

<?php
shell_exec("filename > ~/debug.log 2>&1");

然后在一个单独的shell中:

tail -200f ~/debug.log

当您执行 PHP 脚本时,您的错误和 shell 调用的输出将显示在您的 debug.log 文件中。

【讨论】:

    【解决方案4】:

    还有几点说明

    • 对于调试,请始终将您的 exec/shell_exec 函数包装在 var_dump() 中。

    • error_reporting(-1); 应该打开,display_errors 也应该打开,即使set_error_handler("var_dump"); 也是最后的手段——如果只是为了看看 PHP 本身是否没有调用 execvp 或其他。

    • 使用2&gt;&amp;1(将shell STDERR 合并到STDOUT 流)查看调用失败的原因。
      在某些情况下,您可能需要将命令包装在额外的 shell 调用中:

      // capture STDERR stream via standard shell
      echo shell_exec("/bin/sh -c 'ffmpeg -opts 2>&1' ");
      

      否则,@Mike 建议的 日志文件重定向 是最值得推荐的方法。

    • 在各种 exec 函数之间交替以发现错误消息。虽然它们大多做同样的事情,但输出返回路径各不相同:

      1. exec() → 要么将输出作为函数结果返回,要么通过可选的$output 参数返回。
        还提供了一个$return_var参数,其中包含运行应用程序或shell的errno/退出代码。你可能会得到:

        • ENOENT (2) - 没有这样的文件
        • EIO (127) - IO 错误:找不到文件
        // run command, conjoined stderr, output + error number
        var_dump(exec("ffmpeg -h 2>&1", $output, $errno), $output, $errno));
        
      2. shell_exec() → 是您想要运行的主要用于 shell 样式表达式的内容。
        请务必使用例如分配/打印返回值var_dump(shell_exec("..."));

      3. `` 内联反引号 → 与 shell_exec 相同。

      4. system() → 类似于exec,但总是将输出作为函数结果返回(打印出来!)。此外还允许捕获结果代码。

      5. passthru() → 是另一个exec 替代方案,但总是将任何 STDOUT 结果发送到 PHP 输出缓冲区。这通常使它成为最合适的 exec 包装器。

      6. popen() 或更好的proc_open() → 允许单独捕获 STDOUT 和 STDERR。

    • 大多数 shell 错误在未重定向时会在 PHP 或 Apache error.log 中结束。如果没有产生有用的错误消息,请检查您的 syslog 或 Apache 日志。

    最常见的问题

    • 正如@Kuf 所说:对于过时的虚拟主机计划,您仍然可以找到启用的 safe_modedisable_functions。 PHP exec 函数都不起作用。 (最好找到更好的提供者,否则调查“CGI” - 但不要在不熟悉的情况下安装您自己的 PHP 解释器。)

    • 同样,AppArmor / SELinux / Firejail 有时也可以使用。这些限制了每个应用程序生成新进程的能力。

    • 预期的二进制文件不存在。几乎没有任何虚拟主机预先安装了ffmpeg 之类的工具。您不能在没有准备的情况下运行任意 shell 命令。 有些东西需要安装!

      // Check if `ffmpeg` is actually there:
      var_dump(shell_exec("which ffmpeg"));
      
    • PATH 已关闭。如果您安装了自定义工具,则需要确保它们可以访问。使用 var_dump(shell_exec("ffmpeg -opts")) 将搜索所有常见路径 - 或者正如 Apache 被告知/限制的那样(通常只是 /bin:/usr/bin)。

      检查print_r($_SERVER); PATH 包含的内容以及是否涵盖了您要运行的工具。否则,您可能需要调整服务器设置 (/etc/apache2/envvars),或使用完整路径:

      // run with absolute paths to binary
      var_dump(shell_exec("/bin/sh -c '/usr/local/bin/ffmpeg -opts 2>&1'"));
      

    这有点颠覆了shell的概念。我个人认为这不是可取的。不过,出于安全目的,这确实有意义;此外,当然还可以使用自定义安装。

    • 权限

      1. 为了在 BSD/Linux 系统上运行二进制文件,需要将其设为“可执行”。这就是chmod a+x ffmpeg 所做的。

      2. 此外,此类自定义二进制文件的路径需要 Apache 用户读取,您的 PHP 脚本在该用户下运行。

      3. 更现代的设置使用 PHP 内置 FPM 模式 (suexec+FastCGI),您的虚拟主机帐户与运行 PHP 的帐户相同。

    • 使用 SSH 进行测试。不言而喻,但在通过 PHP 运行命令之前,在真正的 shell 中对其进行测试是非常明智的。用例如探针ldd ffmpeg 如果所有的 lib 依赖项都在那里,否则它可以工作。

    • 使用namei -m /Usr/local/bin/ffmpeg 探测整个路径,如果不确定任何访问权限问题可能来自哪里。

    • 作为 exec 字符串中的命令参数传递的输入值(GET、POST、FILE 名称、用户数据)需要使用escapeshellarg() 进行转义。

      $q = "escapeshellarg";
      var_dump(shell_exec("echo {$q($_GET['text'])} | wc"));
      

      否则你很容易得到 shell 语法错误;并且可能利用稍后安装的代码...

    • 注意不要将反引号与任何 *exec() 函数结合使用:

      $null = shell_exec(`wc file.txt`);
                         ↑           ↑
      

      反引号将运行该命令,并让 shell_exec 保留已运行命令的输出。使用普通引号包裹命令参数。

    • 还可以在 shell 会话中检查预期程序如何使用不同的帐户:

      sudo -u www-data gpg -k
      

      特别是对于 PHP-FPM 设置测试,使用相应的用户 ID。 www-data/apache 主要只用于旧的 mod_php 设置。

      许多 cmdline 工具依赖于某些针对每个用户的配置。此测试通常会揭示缺少的内容。

    • 您无法获得以 … &amp;nohup … 开头的后台运行进程的输出。在这种情况下,您肯定需要使用日志文件重定向exec("cmd &gt; log.txt 2&gt;&amp;1 &amp;");

    在 Windows 上

    • CMD 调用通常不能很好地与 STDERR 流配合使用。

    • 一定要尝试使用 Powershell 脚本来运行其他任何 CLI 应用程序,或者使用如下命令行:

       system("powershell -Command 'pandoc 2>&1'");
      
    • 使用完整路径,并始终首选正斜杠("C:/Program Files/Whatevs/run.exe",如果路径包含空格,则附加引号)。

      自从 MS-DOS 2.0 中引入正斜杠后,它也可以在 Windows 上使用

    • 找出which service and SAM account IIS/Apache 和 PHP 的运行方式。验证它是否具有执行权限。

    • 您通常无法运行 GUI 应用程序。 (典型的解决方法是调用 taskscheduler 或 WMI。)

    PHP → Python, Perl

    如果您从 PHP 调用另一个脚本解释器,那么在出现故障时使用任何可用的调试方法:

    passthru("PYTHONDEBUG=2 python -vvv script.py 2>&1");
    passthru("perl -w script.pl 2>&1");
    passthru("ruby -wT1 script.rb 2>&1");
    

    或者甚至可以先使用任何语法 -c 检查选项运行。

    【讨论】:

    • 很好的答案,我很惊讶它没有更多的赞成票。公认的解决方案不如这个有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-10
    • 2010-10-07
    相关资源
    最近更新 更多