常见功能开发中,需要获取其他命令的输出数据时,通常可以采用 popen 打开子进程进行读操作,但是 popen 并没有提供终止子进程的接口,pclose 会阻塞等待子进程自己结束然后才返回。导致在一些会持续输出的命令调用中,无法方便的结束子进程。

一种解决方法是使用 fork 来自行实现类似 popen 的接口,比如 Kill a process started with popen,但是在一些简单场景,可以尝试更为简单的方法。

popen 在 Linux 上通常是调用 shell 去创建会话执行命令,可以利用 shell 的方法来获取会话进程的 pid,然后在输出中输出 pid ,这样在调用 pclose 之前可以使用 pkill 调用去终止会话及子进程以避免 pclose 阻塞。

shell(常见 bash)中,$$ 变量表示当前 shell 的进程 pid, 我们可以利用该变量来控制子进程。以 ping 指令为例,使用如下命令创建子进程:

1
2
3
# 需要注意 \$\$ 而非 $$,否则当前 shell 会直接解译该变量导致输出为当前 shell pid,
# 而非会话子进程
sh -c "echo pid=\$\$; ping 223.5.5.5"

其输出:

1
2
3
4
5
6
7
pid=62853
PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.
64 bytes from 223.5.5.5: icmp_seq=1 ttl=116 time=24.1 ms
64 bytes from 223.5.5.5: icmp_seq=2 ttl=116 time=23.3 ms
64 bytes from 223.5.5.5: icmp_seq=3 ttl=116 time=24.4 ms
64 bytes from 223.5.5.5: icmp_seq=4 ttl=116 time=23.4 ms
...

这时,在另外 shell 中执行 pkill -P 62883 来终止会话子进程(该命令无需 root 权限, pkill -P 只终止父进程 pid 匹配的进程, shell 进程在子进程命令结束后自然退出),子会话及其子进程会终止退出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pid=62853
PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.
64 bytes from 223.5.5.5: icmp_seq=1 ttl=116 time=24.6 ms
64 bytes from 223.5.5.5: icmp_seq=2 ttl=116 time=24.7 ms
64 bytes from 223.5.5.5: icmp_seq=3 ttl=116 time=24.5 ms
64 bytes from 223.5.5.5: icmp_seq=4 ttl=116 time=24.2 ms
64 bytes from 223.5.5.5: icmp_seq=5 ttl=116 time=22.6 ms
64 bytes from 223.5.5.5: icmp_seq=6 ttl=116 time=21.2 ms
64 bytes from 223.5.5.5: icmp_seq=7 ttl=116 time=26.3 ms
64 bytes from 223.5.5.5: icmp_seq=8 ttl=116 time=24.7 ms
Terminated

利用该特性,在实践中可以在 popen 后,利用 fgets 从输出中获取会话 pid,然后在 pclose 前,调用 system("pkill -P xxxx") 来终止会话子进程,然后再调用 pclose 去释放资源,此时 pclose 调用不会阻塞, 该方法虽不优雅,但足够 quick-and-dirty.