php cli脚本需满足生产级可靠性:首行写#!/usr/bin/env php且无bom,chmod +x后用./script.php执行;参数用getopt()解析更健壮;必须规范使用exit(0/1)和fwrite(stderr)确保可被cron及shell正确调用与排查。

PHP命令行脚本不是“能跑就行”的玩具,而是要能进生产环境、被cron调用、被运维排查、被其他脚本依赖的可靠组件。它和Web脚本根本不是一回事——没有请求生命周期,没有$_GET,没有自动输出缓冲清理,错误不藏,退出码不糊弄。
如何让PHP脚本直接执行(不用写 php script.php)
关键就两步:Shebang + 执行权限。但错一个字符就失败。
-
#!/usr/bin/env php必须是文件第一行,且不能有BOM、空格或UTF-8签名;Windows记事本保存的文件极大概率带BOM,会报bad interpreter: No such file or directory -
chmod +x script.php后,用./script.php运行;若提示Permission denied,检查当前用户是否对文件有读+执行权限(ls -l script.php看权限位) - 不要硬写
#!/usr/bin/php—— macOS Homebrew、Docker容器、多PHP版本共存环境下,路径常不一致;env会从$PATH中找第一个php,更稳妥
$argv 和 $argc 的真实行为边界
$argv 看似简单,但几个细节不注意就会漏参数或越界:
-
$argv[0]是脚本路径(如./deploy.php或/home/user/scripts/deploy.php),不是固定字符串;用basename($argv[0])提取命令名更安全 - 空格分隔参数,但引号包裹的内容算一个整体:
./script.php "hello world" --force→$argv[1] === "hello world",$argv[2] === "--force" - 如果脚本通过
php script.php调用,$argv[0]是script.php;如果用./script.php,$argv[0]是./script.php—— 不要假设格式统一 - 参数为空时(如
./script.php ""),$argv[1]存在但为空字符串,isset($argv[1])为 true,需用!empty($argv[1])判断是否有有效值
为什么 getopt() 比手撕 $argv 更值得优先用
当参数超过2个、需要支持 -v / --verbose / --timeout=30 这类格式时,自己遍历 $argv 容易出逻辑漏洞,而 getopt() 是PHP内置、C层实现、经得起压测的解析器。
- 短选项:
getopt('hvq', ['help', 'verbose', 'quiet'])支持-h、-v、-hq连写,也接受--help - 带值选项必须加冒号:
'p:'表示-p 8080或-p8080都合法;'p::'表示值可选(-p或-p8080) - 返回数组键为选项字母/长名,值为传入的值(或
true);未识别的参数留在$argv剩余部分,可继续处理 - 注意:
getopt()不处理位置参数(如./backup.php prod /var/www中的prod和/var/www),这些仍需手动从清理后的$argv中提取
CLI脚本里最容易被忽略的退出码和错误流
很多脚本只用 echo 和 exit(0),结果被 cron 或 shell 脚本调用时完全无法判断成败。
- 成功必须用
exit(0);任何非0值(如exit(1))表示失败,cron会据此发告警,&&链式命令会中断 - 错误信息必须输出到
STDERR,而非STDOUT:fwrite(STDERR, "Connection failed\n");—— 否则重定向日志(> log.txt 2>&1)时错误会被淹没 - 捕获异常后别只
echo,要fwrite(STDERR, $e->getMessage()."\n"); exit(1); - 长时间运行脚本建议在关键步骤后调用
fflush(STDOUT)和fflush(STDERR),避免输出卡在缓冲区导致日志断更
CLI脚本真正的复杂点不在语法,而在它暴露在操作系统底层:信号响应(SIGTERM)、进程父子关系、文件描述符泄漏、时区与locale继承、以及——当别人用 nohup ./script.php & 启动时,你的脚本是否还知道 STDIN 已关闭。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











