我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

解决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的逻辑, 需要根据场景选择使用方式

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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

使用Mybatis遇到的坑之Integer类型参数问题怎么解决

这篇文章主要讲解了“使用Mybatis遇到的坑之Integer类型参数问题怎么解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“使用Mybatis遇到的坑之Integer类型参数问题怎么解决
2023-07-05

Matlab中的mat数据转成python中使用的npy数据遇到的坑及解决

这篇文章主要介绍了Matlab中的mat数据转成python中使用的npy数据遇到的坑及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2022-12-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录