PHP 可以检测它是从 cron 作业还是从命令行运行?

Posted

技术标签:

【中文标题】PHP 可以检测它是从 cron 作业还是从命令行运行?【英文标题】:Can PHP detect if its run from a cron job or from the command line? 【发布时间】:2010-09-16 11:41:19 【问题描述】:

我正在寻找 php 的方法来检测脚本是从 shell 上的手动调用运行(我登录并运行它),还是从 crontab 条目运行。

我有各种用 php 编写的维护类型脚本,已设置为在我的 crontab 中运行。有时,我需要提前手动运行它们,或者如果出现故障/损坏,我需要运行它们几次。

问题在于,我在任务(发布到 Twitter、发送电子邮件等)中还设置了一些外部通知,我不想在每次手动运行脚本时发生这些通知。

我使用的是 php5(如果重要的话),它是一个相当标准的 linux 服务器环境。

有什么想法吗?

【问题讨论】:

添加为链接问题:Detect if a PHP script is being run interactively or not @Leigh:感谢您的参考! 【参考方案1】:

据我所知,最简单的解决方案可能是自己提供一个额外的参数来告诉脚本它是如何被调用的。

【讨论】:

【参考方案2】:

我会查看$_ENV(var_dump() 它)并检查您在运行它时是否注意到与 cronjob 运行它时的差异。除此之外,我认为没有“官方”开关可以告诉您发生了什么。

【讨论】:

【参考方案3】:

在 cron 命令中,将?source=cron 添加到脚本路径的末尾。然后,在您的脚本中,检查$_GET['source']

编辑:抱歉,这是一个 shell 脚本,所以不能使用 qs。我认为,您可以以php script.php arg1 arg2 的形式传递参数,然后使用$argv 读取它们。

【讨论】:

这是一个shell脚本,不是网页。【参考方案4】:

你可以设置一个额外的参数,或者在你的 crontab 中添加一行,也许:

CRON=running

然后您可以检查“CRON”的环境变量。另外,请尝试检查 $SHELL 变量,我不确定 cron 是否/将其设置为什么。

【讨论】:

我从来不知道你可以像这样在 crontab 中放置其他命令/变量行。我只学过定时命令的语法。这加上 php $_ENV 和 $_SERVER,将有助于我想要实现的目标。谢谢 "man 5 crontab" 了解所有细节。但是,是的,您可以设置环境变量。通常这用于设置 PATH 或 SHELL,但你可以设置任何你喜欢的。 只是想注意variables_order 必须在 php.ini 中包含“E”才能填充$_ENV 数组 @andrewtweber:你应该仍然可以使用getenv(),除非你被安全模式阻止了。 这是一个聪明的技巧 - 我刚刚成功使用它,谢谢。【参考方案5】:

我不具体了解 PHP,但您可以遍历进程树,直到找到 init 或 cron。

假设 PHP 可以获取它自己的进程 ID 并运行外部命令,则应该执行 ps -ef | grep pid 其中 pid 是您自己的进程 ID 并从中提取父进程 ID (PPID)它。

然后对该 PPID 执行相同的操作,直到您将 cron 作为父级或 init 作为父级。

例如,这是我的进程树,你可以看到所有权链,1 -> 6386 -> 6390 -> 6408。

UID     PID  PPID  C  STIME  TTY        TIME  CMD
root      1     0  0  16:21  ?      00:00:00  /sbin/init
allan  6386     1  0  19:04  ?      00:00:00  gnome-terminal --geom...
allan  6390  6386  0  19:04  pts/0  00:00:00  bash
allan  6408  6390  0  19:04  pts/0  00:00:00  ps -ef

在 cron 下运行的相同进程如下所示:

UID     PID  PPID  C  STIME  TTY        TIME  CMD
root      1     0  0  16:21  ?      00:00:00  /sbin/init
root   5704     1  0  16:22  ?      00:00:00  /usr/sbin/cron
allan  6390  5704  0  19:04  pts/0  00:00:00  bash
allan  6408  6390  0  19:04  pts/0  00:00:00  ps -ef

这种“遍历进程树”解决方案意味着您不必担心引入人为参数来指示您是否在 cron 下运行 - 您可能会忘记在交互式会话中执行此操作并填充东西起来。

【讨论】:

大概,您将它设置为“无参数”意味着您以交互方式运行,这样您就不会忘记它。不过有趣的解决方案。 是的,非常有趣的方法。也可以用于其他非 php 的东西。【参考方案6】:

与检测脚本何时从 crontab 运行相比,手动运行它可能更容易检测。

当您从命令行运行脚本时,会设置很多环境变量(在 $_ENV 数组中)。这些将根据您的服务器设置和登录方式而有所不同。在我的环境中,以下环境变量是在手动运行脚本时设置的,而从 cron 运行时不存在:

期限 SSH_CLIENT SSH_TTY SSH_CONNECTION

还有其他的。因此,例如,如果您总是使用 SSH 访问该框,那么下面的行将检测脚本是否从 cron 运行:

$cron = !isset($_ENV['SSH_CLIENT']);

【讨论】:

经过调查,这似乎是最好的方法。在 _ENV + _SERVER 之间,我很确定我可以自信地检测到谁/在哪里/谁在运行它,并采取相应的行动。 crontab 中的 CRON=running 行也会有所帮助。 似乎默认的 php.ini 已经随着时间的推移而更改为默认不填充 $_ENV 全局。一种安全的方法是使用 getenv('SSH_CLIENT') - 请参阅此处了解更多信息***.com/questions/3780866/why-is-my-env-empty【参考方案7】:

$_SERVER['SESSIONNAME'] 包含 Console 如果从 CLI 运行。也许这有帮助。

【讨论】:

我会使用 php_sapi_name() - 但这仍然无法区分 cron 和交互式 CLI 执行。【参考方案8】:
if(!$_SERVER['HTTP_HOST']) 
 blabla();

【讨论】:

这个对我来说最有意义,除了我会使用 if (!isset($_SERVER['HTTP_HOST'])) blah(); . 我很确定 $_SERVER['HTTP_HOST'] 在运行两个 Cron 或 CLI 运行脚本时不存在。最初的海报是为了区分 Cron 和 CLI,而不是 CLI 和 Web。【参考方案9】:

我认为在命令行中运行 cron 命令会更好,您不会手动运行该选项。

cron 会做:

command ext_updates=1

手动会做:

command 

只需在脚本本身中添加一个选项,让 ext_updates 参数的默认值为 false。

【讨论】:

【参考方案10】:

在我的环境中,我发现如果从命令行运行,$_SERVER 中设置了TERM,但如果通过 Apache 作为 Web 请求运行,则没有设置。我把它放在我可以从命令行运行的脚本的顶部,或者可以通过网络浏览器访问:

if (isset($_SERVER'TERM'))

    class::doStuffShell();

else

    class::doStuffWeb();

【讨论】:

感谢这帮助我使用 Web 浏览器和命令行【参考方案11】:

正确的方法是使用 posix_isatty() 函数,例如stdout 文件描述符,如下所示:

if (posix_isatty(STDOUT))
    /* do interactive terminal stuff here */

【讨论】:

这是我一直在寻找的,虽然在我的系统(和 TTY)上运行它,但我收到警告:PHP Warning: posix_isatty(): cannot seek on a pipe in /root/test.php on line 3,所以我在调用前使用了@使警告静音。另请注意,此方法将检测命令行中的调用,这些调用通过管道传输到“不是 tty”——这基本上是我想要的,但是 YMMV。 @Guss:迟到了,但管道未注册为 TTY 的原因是,如果您被传送到其他地方,则您的 STDOUT 不是 TTY;您的 STDOUT 是另一个应用程序 STDIN,而不是您正在运行它的 TTY。我相信管道中的最后一个应用程序仍会在其 STDOUT 上看到 TTY。 是的,你是对的,这与我期望得到的行为一致。我想看看它是不是一个 TTY,所以我可以做一些屏幕操作——我显然不能在管道上做 :-)【参考方案12】:

posix_isatty(STDOUT) return FALSE 如果 cli 调用的输出被重定向(管道或文件)...

【讨论】:

【参考方案13】:

这对我来说很容易......只需count($_SERVER['argc']),如果您的结果高于零,它将耗尽服务器。您只需将您的自定义变量添加到您的$_SERVER['argv'],例如"CronJob"=true;

【讨论】:

【参考方案14】:

令人毛骨悚然。试试看

if (!isset($_SERVER['HTTP_USER_AGENT'])) 

相反。 PHP客户端二进制不发送它。 Term Type 只适用于 PHP 作为模块(即 apache),但是当通过 CGI 接口运行 php 时,使用上面的示例!

【讨论】:

【参考方案15】:

这是我用来发现脚本从哪里执行的。更多信息请查看 php_sapi_name 函数:http://www.php.net/manual/en/function.php-sapi-name.php

$sapi_type = php_sapi_name();
if(substr($sapi_type, 0, 3) == 'cli' || empty($_SERVER['REMOTE_ADDR'])) 
    echo "shell";
 else 
    echo "webserver";

编辑: 如果php_sapi_name() 不包含cli(可能是clicli_server),那么我们检查$_SERVER['REMOTE_ADDR'] 是否为空。从命令行调用时,这应该是空的。

【讨论】:

来自 cmets:请注意,php-cgi 二进制文件可以从命令行、shell 脚本或 cron 作业调用!如果是这样,php_sapi_name() 将始终返回相同的值(即“cgi-fcgi”),而不是您所期望的“cli”。 @pierdevara 我不知道!我会相应地编辑我的帖子。感谢您指出这一点。【参考方案16】:

我认为最通用的解决方案是在cron命令中添加一个环境变量,并在代码中查找。它适用于所有系统。

如果cron执行的命令是,例如:

"/usr/bin/php -q /var/www/vhosts/myuser/index.php"

改成

"CRON_MODE=1 /usr/bin/php -q /var/www/vhosts/myuser/index.php"

然后你可以在代码上查看:

if (!getenv('CRON_MODE'))
    print "Sorry, only CRON can access this script";

【讨论】:

谢谢,这有帮助。【参考方案17】:
if (php_sapi_name() == 'cli')    
   if (isset($_SERVER['TERM']))    
      echo "The script was run from a manual invocation on a shell";   
    else    
      echo "The script was run from the crontab entry";   
      
 else  
   echo "The script was run from a webserver, or something else";   

【讨论】:

最佳答案在这里!我只是将它定义为我的常量之一:define( 'PHP_SAPI', ( php_sapi_name() == 'cli' ) ? ( isset( $_SERVER['TERM'] ) ? 1 : 2 ) : 0 ); 这绝对是最好的。回答所有可能的问题。 (就个人而言,我来到这里想知道 web 与 cron,所以这很棒!) 我不确定过去几年是否发生了变化,但在我的系统(CentOS 6.6,PHP 5.4.38,运行 Litespeed)上,php_sapi_name() 运行时返回 cgi-fcgi克朗。换句话说,如果您的系统和我的系统一样,您可能希望将此答案代码的第一行替换为:if (php_sapi_name() == 'cli' || php_sapi_name() == 'cgi-fcgi') windows 没有$_SERVER['term']【参考方案18】:

这很容易。 Cron 守护进程总是导出MAILTO 环境变量。检查它是否存在并且具有非空值 - 然后您从 cron 运行。

【讨论】:

@shomeax 为什么你认为这不是真的?不比以前的方法差,通常 MAILTO 有一些价值(即使它等于 USER),因为输出总是通过电子邮件传递。 通常 MAILTO 在 crontab 中设置,但也可以不设置。只是停留在证明了这一点的共享主机设置中。因此,对于公共领域脚本,最好的方法是检查术语变量或使用@agi 建议的强制添加的环境变量【参考方案19】:
getenv('TERM')

填充 SO 的 30 字符分钟。

【讨论】:

【参考方案20】:

另一种选择是测试一个特定的环境变量,该变量是在通过 Web 调用 php 文件时设置的,如果由命令行运行则不设置。

在我的网络服务器上,我正在测试 APACHE_RUN_DIR 环境变量是否设置如下:

if (isset($_ENV["APACHE_RUN_DIR"])) 
  // I'm called by a web user

else 
  // I'm called by crontab
 

为了确保它可以在您的网络服务器上运行,您可以使用以下语句在您的网络服务器上放置一个虚拟 php 文件:

<?php var_dump($_ENV);  ?>

然后 1) 使用您的网络浏览器加载它,然后 2) 像这样从命令行加载它

/usr/bin/php /var/www/yourpath/dummy.php

比较差异并测试适当的变量。

【讨论】:

【参考方案21】:

在我的 Amazon Linux 服务器上,这对我有用:

$isCron = ($_SERVER['HOME'] == '/');

如果您只是运行它,主目录将设置为您的。如果使用 sudo 运行,主目录设置为 /root。

【讨论】:

【参考方案22】:

(array) $argv 将在 cron 作业运行时设置。 数组的第一个值将是您在创建 cron 作业时使用的 /path/to/file。 $argv 中的以下值将是 /path/to/file 之后的任何参数。

例如,如果您的 cron 作业命令如下所示:

php /path/to/file.php first second third

$argv 的值将是:["/path/to/file.php", "first", "second", "third"]

然后你可以:

if (isset($argv) &amp;&amp; is_array($argv) &amp;&amp; in_array('first', $argv)) /* do something */

【讨论】:

以上是关于PHP 可以检测它是从 cron 作业还是从命令行运行?的主要内容,如果未能解决你的问题,请参考以下文章

msbuild 是不是知道它是从 Visual Studio 调用还是从命令行调用?

是否有任何 Cron 工作替代方案?

从命令行运行但不能通过 cron 运行时脚本运行良好

如何使用 php 中的 cron 作业自动删除 mysql 行?

cron 作业会杀死最后一个 cron 执行吗?

仅在尚未运行时运行 cron 作业