请记住,在多进程应用程序(最适合 CLI)中,I/O 操作通常会阻塞信号被处理。
例如,如果您有一个父进程正在等待 fread(STDIN),它不会处理 SIGCHLD,即使您为此定义了信号处理程序,直到 fread 调用返回之后才会处理。
在这种情况下,您的解决方案是等待 stream_select() 以找出读取是否会阻塞。关键的是,等待 stream_select() 不会阻塞信号被处理。
Aurelien
CLI SAPI 定义了一些用于 I/O 流的常量,以使命令行编程更容易。
常量 | 描述 |
---|---|
STDIN |
一个已打开的流指向 <?php stdin 读取单行,可以使用以下代码:<?php |
STDOUT |
一个已打开的流指向 <?php |
STDERR |
一个已打开的流指向 <?php |
鉴于以上,您不需要自己打开例如 stderr
的流,只需使用常量代替流资源即可。
php -r 'fwrite(STDERR, "stderr\n");'
注意:
如果从
stdin
读取 PHP 脚本,则这些常量不可用。
请记住,在多进程应用程序(最适合 CLI)中,I/O 操作通常会阻塞信号被处理。
例如,如果您有一个父进程正在等待 fread(STDIN),它不会处理 SIGCHLD,即使您为此定义了信号处理程序,直到 fread 调用返回之后才会处理。
在这种情况下,您的解决方案是等待 stream_select() 以找出读取是否会阻塞。关键的是,等待 stream_select() 不会阻塞信号被处理。
Aurelien
STDIN 中的命令行界面数据只有在按下回车键后才会可用。
通过在第一次读取 STDIN 之前添加 "readline_callback_handler_install('', function(){});",可以捕获单个按键。
注意:这似乎只在 Linux CLI 下工作,在 Apache 或 Windows CLI 中不工作。
这可以用于隐藏密码,也可以与 'stream_select' 一起使用,以创建非阻塞键盘监控器。
<?php
// 没有 readline_callback_handler_install('', function(){}); 的演示
$resSTDIN=fopen("php://stdin","r");
echo("输入 'x'。然后按回车.");
$strChar = stream_get_contents($resSTDIN, 1);
echo("\n你输入了: ".$strChar."\n\n");
fclose($resSTDIN);
// 有 readline_callback_handler_install('', function(){}); 的演示
// 这行代码消除了对 STDIN 上 <CR> 的等待
readline_callback_handler_install('', function(){});
$resSTDIN=fopen("php://stdin","r");
echo("我们现在运行了: readline_callback_handler_install('', function(){});\n");
echo("按下 'y' 键");
$strChar = stream_get_contents($resSTDIN, 1);
echo("\n你按下了: ".$strChar."\n但没有必要按 <cr>\n");
fclose($resSTDIN);
readline_callback_handler_remove ();
echo("\n再见\n")
?>
它还从 CLI 隐藏文本,因此可用于诸如密码隐藏之类的事情。
例如
<?php
readline_callback_handler_install('', function(){});
echo("输入密码,然后按回车。(不要使用真实密码!)\n");
echo("密码: ");
$strObscured='';
while(true)
{
$strChar = stream_get_contents(STDIN, 1);
if($strChar===chr(10))
{
break;
}
$strObscured.=$strChar;
echo("*");
}
echo("\n");
echo("你输入了: ".$strObscured."\n");
?>
以下代码展示了如何测试 STDIN 上的输入。在本例中,我们正在寻找 CSV 数据,因此我们使用 fgetcsv 读取 STDIN,如果它创建了一个数组,我们假设 STDIN 上有 CVS 输入,如果没有创建数组,我们假设 STDIN 上没有输入,并且之后会查找带有 CSV 文件名的参数。
注意,如果没有 stream_set_blocking() 调用,fgetcsv() 会挂在 STDIN 上,等待用户的输入,这在寻找管道文件时并没有用,因为如果文件不存在,它将永远不会出现。
<?php
stream_set_blocking(STDIN, 0);
$csv_ar = fgetcsv(STDIN);
if (is_array($csv_ar)){
print "STDIN 上的 CVS\n";
} else {
print "在 ARGV 中查找 CSV 文件名。\n";
}
?>
在 Linux CLI 下 - STDIN、STDOUT 和 STDERR 可以关闭并重新连接到不同的 php 流,例如文件、管道甚至 UDP socket_stream。(我使用这种技术将长时间运行的后台脚本的输出/错误发送到文件,以便在出现问题时进行调试。)
例如:(以下代码创建/追加文件 "/tmp/php_stdout.txt")
<?php
// 此代码仅在 Linux 的 CLI 下工作
// 注意:在关闭 STDOUT 之前,STDOUT 不会以 $ 为前缀
// 获取 STDOUT 当前控制台的路径,以便稍后重新连接!
$strOldSTDOUT=(posix_ttyname(STDOUT));
echo("这将输出到当前控制台\r\n");
// 关闭 STDOUT 资源
fclose(STDOUT);
// 将 $STDOUT 重新打开为文件,注意:所有后续 $STDOUT 使用将以 $ 为前缀
$STDOUT=fopen("/tmp/php_stdout.txt","a"); /
echo("这应该追加到文件 /tmp/php_stdout.txt\r\n");
// 再次关闭 stdout 以便重新连接控制台。注意:我们仍在使用
fclose($STDOUT);
// 使用我们之前获取的控制台路径
$STDOUT=fopen($strOldSTDOUT,"r+");
echo("我们已经回到控制台\r\n");
?>
我发现 STDIN 常量有一个 BUG,我不知道是否是 Enter/Return 键导致了这个问题,当使用 trim(fgets(STDIN)) 时,它不会修剪任何内容,当我检测 fgets(STDIN) 的长度时,在 Windows 中,它比我输入的字符多 2 个,在 Linux 中,它多 1 个。我尝试了 trim(fgets(STDIN), ' \r\n'),但它仍然不起作用。
所以,我必须手动用 substr 对输入进行截取,似乎是这样的
<?php
$STDIN = trim(substr(fgets(STDIN), 0, (PHP_OS == 'WINNT' ? 2 : 1)));
?>
然后我得到了我真正想要的东西。