今天用php在后台跑一些常规任务的时候使用到了exec函数,它用来执行Linux系统函数进行相关统计,如exec("ls -l")
,但是却发生了很诡异的问题,真的是特别诡异的问题,首先看下面这段代码
问题引入
$shell = 'date';
# 执行Linux下的 date 命令,输出当前日期
exec($shell, $result);
var_dump($result);
//结果如下:
array(1) {
[0]=>
string(42) "2016年 09月 29日 星期四 21:44:20 CST"
}
$shell = 'echo "1\n2\n3"';
exec($shell, $result);
var_dump($result);
// 按理说结果应该应该如下啊,exec会把shell执行的结果每一行作为一个数组元素最终赋值给$result
// 期望值
array(3) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[2]=>
string(1) "3"
}
// 但最终结果却是下面的结果:
array(4) {
[0]=>
string(42) "2016年 09月 29日 星期四 21:44:20 CST"
[1]=>
string(1) "1"
[2]=>
string(1) "2"
[3]=>
string(1) "3"
}
你大概能够看明白发生什么了吧,对,exec会将结果
追加
到$result
中去!
每执行一次exec都会将新的结果追加进去!所以最终 $result 的结果并不是你想要的单纯的123,而是在之前 date 的基础上追加了123!
情景复现
所以,我当时依赖exec执行结果
进行逻辑处理时,下面的代码是会出坑的
假设:
- 我们对shell输出结果进行处理和使用
- 执行的shell命令会把结果输出为一行,如 wc -l
- 那么exec的结果$result就是一个数组,并且只包含一个元素,就是输出的数据行
- 我们取
$result[0]
就是需要的shell命令执行结果
$shell = 'date';
for ($i = 0; $i < 3; $i++) {
exec($shell, $result);
sleep(1); // 模拟每次时间不同,shell结果不同
// 这里是我们对输出结果进行处理和使用
// 假设我们的shell会把结果输出为一行,打印到屏幕上
// 那么 $result 数组应该就只包含一个元素,就是输出的数据行
// 我们取 $result[0] 就是需要的结果
var_dump($result[0]); // 假设这里是对结果的应用,后续依据这个进行其他操作
}
你知道结果是啥么?竟然是这个:
string(42) "2016年 09月 29日 星期四 22:02:26 CST"
string(42) "2016年 09月 29日 星期四 22:02:26 CST"
string(42) "2016年 09月 29日 星期四 22:02:26 CST"
为啥是一样的呢!!至少每个结果不一样,应该每个多一秒才对吧!!
过程分析
循环执行第一次时, $result是
array(3) {
[0]=>
string(42) "2016年 09月 29日 星期四 22:02:26 CST"
}
第二次时
array(3) {
[0]=>
string(42) "2016年 09月 29日 星期四 22:02:26 CST"
[1]=>
string(42) "2016年 09月 29日 星期四 22:02:27 CST"
}
第三次时
array(3) {
[0]=>
string(42) "2016年 09月 29日 星期四 22:02:26 CST"
[1]=>
string(42) "2016年 09月 29日 星期四 22:02:27 CST"
[12=>
string(42) "2016年 09月 29日 星期四 22:02:28 CST"
}
分析一下,就是因为exec每次将结果追加到 $result 中导致的,每次循环shell执行的结果都追加在 $result 的尾部,然后我们每次用 $result[0]
作为预期数据时,实际上每次取的永远是第一次exec的结果,而不是我们想要的后面执行的而结果 囧~
解决方法
实际上 php.net 上官方已经明显的标注了,还是没认真读文档的锅
如果提供了 output 参数, 那么会用命令执行的输出填充此数组, 每行输出填充数组中的一个元素。 数组中的数据不包含行尾的空白字符,例如 \n 字符。 请注意,如果数组中已经包含了部分元素,exec() 函数会在数组末尾追加内容。如果你不想在数组末尾进行追加, 请在传入 exec() 函数之前 对数组使用 unset() 函数进行重置。
方法1
如果要多次使用exec的值,那么每次在exec之前清空$result即可
// 每次使用 $result 进行unset清空操作
unset($result);
exec($shell, $result);
方法2
但其实还有更简单的方法,直接用 shell_exec
也行,shell_exec不会有类似追加问题,直接将标准输出赋值到 $result 中
$result = shell_exec($shell);