解决python subprocess参数shell=True踩到的坑

 更新时间:2021年04月23日 09:35:55   作者:le31ei  
这篇文章主要介绍了解决python subprocess参数shell=True踩到的坑,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

0x01 问题现象

写的程序使用subprocess创建子进程运行其他程序,判断其他程序运行完后进行处理。

在subprocess使用了shell=True,判断用户程序退出的代码如下

while self.proc.poll() is None:
    do_something

判断子进程是否运行结束,程序在子进程运行结束后,代码未向下继续运行,而是卡在了这个循环中。

0x02 原因分析

百度后对shell参数的解释如下:

shell=True参数会让subprocess.Popen接受字符串类型的变量作为命令,并调用shell去执行这个字符串,当shell=False是,subprocess.Popen只接受数组变量作为命令,并将数组的第一个元素作为命令,剩下的全部作为该命令的参数。

通过查看服务器进程可以看到,仍然有进程存在,进程如下

这里写图片描述

为shell中运行的程序,由此可以得出,shell=true时,子进程在运行完后,shell并没有退出,而是卡在shell命令中,可由进程看到。

这里写图片描述

补充:Python踩坑之旅其一杀不死的Shell子进程

1.1 踩坑案例

踩坑的程序是个常驻的Agent类管理进程, 包括但不限于如下类型的任务在执行:

a. 多线程的网络通信包处理

和控制Master节点交互

有固定Listen端口

b. 定期作业任务, 通过subprocess.Pipe执行shell命令

c. etc

发现坑的过程很有意思:

a.重启Agent发现Port被占用了

=> 立刻想到可能进程没被杀死, 是不是停止脚本出问题

=> 排除发现不是, Agent进程确实死亡了

=> 通过 netstat -tanop|grep port_number 发现端口确实有人占用

=> 调试环境, 直接杀掉占用进程了之, 错失首次发现问题的机会

b.问题在一段时间后重现, 重启后Port还是被占用

定位问题出现在一个叫做xxxxxx.sh的脚本, 该脚本占用了Agent使用的端口

=> 奇了怪了, 一个xxx.sh脚本使用这个奇葩Port干啥(大于60000的Port, 有兴趣的砖友可以想下为什么Agent默认使用6W+的端口)

=> review该脚本并没有进行端口监听的代码

一拍脑袋, c.进程共享了父进程资源了

=> 溯源该脚本,发现确实是Agent启动的任务中的脚本之一

=> 问题基本定位, 该脚本属于Agent调用的脚本

=> 该Agent继承了Agent原来的资源FD, 也就是这个port

=> 虽然该脚本由于超时被动触发了terminate机制, 但terminate并没有干掉这个子进程

=> 该脚本进程的父进程(ppid) 被重置为了1

d.问题****出在脚本进程超时kill逻辑

1.2 填坑解法

通过代码review, 找到shell具体执行的库代码如下:

self._subpro = subprocess.Popen(
    cmd, shell=True, stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    preexec_fn=_signal_handle
)
# 重点是shell=True !

把上述代码改为:

self._subpro = subprocess.Popen(
    cmd.split(), stdout=subprocess.PIPE,
    stderr=subprocess.PIPE, preexec_fn=_signal_handle
)
# 重点是去掉了shell=True

1.3 坑位分析

Agent会在一个新创建的threading线程中执行这段代码, 如果线程执行时间超时(xx seconds), 会调用 self._subpro.terminate()终止该脚本.

表面正常:

启用新线程执行该脚本

如果出现问题,执行超时防止hang住其他任务执行调用terminate杀死进程

深层问题:

Python 2.7.x中subprocess.Pipe 如果shell=True, 会默认把相关的pid设置为shell(sh/bash/etc)本身(执行命令的shell父进程), 并非执行cmd任务的那个进程

子进程由于会复制父进程的opened FD表, 导致即使被杀死, 依然保留了拥有这个Listened Port FD

这样虽然杀死了shell进程(未必死亡, 可能进入defunct状态), 但实际的执行进程确活着. 于是1.1中的坑就被结实的踩上了.

1.4 坑后扩展

1.4.1 扩展知识

本节扩展知识包括二个部分:

Linux系统中, 子进程一般会继承父进程的哪些信息

Agent这种常驻进程选择>60000端口的意义

扩展知识留到下篇末尾讲述, 感兴趣的可以自行搜索

1.4.1 技术关键字

Linux系统进程

Linux随机端口选择

程序多线程执行

Shell执行

1.5 填坑总结

1.子进程会继承父进程的资源信息

2.如果只kill某进程的父进程, 集成了父进程资源的子进程会继续占用父进程的资源不释放, 包括但不限于

listened port

opened fd

etc

3.Python Popen使用上, shell的bool状态决定了进程kill的逻辑, 需要根据场景选择使用方式

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。如有错误或未考虑完全的地方,望不吝赐教。

相关文章

  • Python+Tkinter实现经典井字棋小游戏

    Python+Tkinter实现经典井字棋小游戏

    Tkinter是内置到Python安装包中的,只要安装好Python之后就能import Tkinter,而且IDLE也是用Tkinter编写而成的。本文将用Tkinter编写经典的井字棋小游戏,需要的可以参考一下
    2022-03-03
  • 基于python实现的抓取腾讯视频所有电影的爬虫

    基于python实现的抓取腾讯视频所有电影的爬虫

    这篇文章主要介绍了用python实现的抓取腾讯视频所有电影的爬虫,这个程序使用芒果存, 所以大家需要下载使用mongodb才可以
    2016-04-04
  • pytorch 预训练模型读取修改相关参数的填坑问题

    pytorch 预训练模型读取修改相关参数的填坑问题

    这篇文章主要介绍了pytorch 预训练模型读取修改相关参数的填坑问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • OpenCV实现对象跟踪的方法

    OpenCV实现对象跟踪的方法

    OpenCV 是一个很好的处理图像和视频的工具,本文主要介绍了OpenCV 进行对象跟踪,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • Python实现ssh批量登录并执行命令

    Python实现ssh批量登录并执行命令

    本篇文章主要是介绍了Python实现ssh批量登录并执行命令,有一些任务可以进行批量完成,Python就可以完成,有需要的同学可以了解一下。
    2016-10-10
  • Ubuntu中配置TensorFlow使用环境的方法

    Ubuntu中配置TensorFlow使用环境的方法

    这篇文章主要介绍了Ubuntu中配置TensorFlow使用环境的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04
  • python字符串拼接和列表拼接方式

    python字符串拼接和列表拼接方式

    这篇文章主要介绍了python字符串拼接和列表拼接方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • 使用Python实现一个简单的项目监控

    使用Python实现一个简单的项目监控

    这篇文章主要介绍了使用Python实现一个简单的项目监控,包括连接数据库进行查询等操作,需要的朋友可以参考下
    2015-03-03
  • Python如何提取chm数据

    Python如何提取chm数据

    这篇文章主要介绍了Python如何提取chm数据,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-01-01
  • 举例区分Python中的浅复制与深复制

    举例区分Python中的浅复制与深复制

    这篇文章主要介绍了举例区分Python中的浅复制与深复制,是Python入门学习中的重要知识,需要的朋友可以参考下
    2015-07-07

最新评论