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

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

(0)

相关推荐

  • python3通过subprocess模块调用脚本并和脚本交互的操作

    因工作需要,需实现如题所示功能.查阅网上博客,资料,大多都是针对python2的,而且很多地方不明所以,所以自己整理了一下查阅的结果,重新写一篇博客. 预备知识 1.python3的默认字符串类型 Python 2.x 同时支持ASCII和 Unicode字符串,默认情况下是ASCII编码.而 Python 3中这种支持刚好调换:Unicode现在变成了默认类型,而 ASCII 字符串现在称为 bytes. bytes 数据结构包含字节值,并且它 不应该再被视为一个字符串,因为它是一个包含数据的

  • 详谈python中subprocess shell=False与shell=True的区别

    shell=True参数会让subprocess.call接受字符串类型的变量作为命令,并调用shell去执行这个字符串,当shell=False是,subprocess.call只接受数组变量作为命令,并将数组的第一个元素作为命令,剩下的全部作为该命令的参数. 举个例子来说明 from subprocess import call import shlex cmd = "cat test.txt; rm test.txt" call(cmd, shell=True) 上述脚本中,sh

  • 使用python执行shell脚本 并动态传参 及subprocess的使用详解

    最近工作需求中 有遇到这个情况 在web端获取配置文件内容 及 往shell 脚本中动态传入参数 执行shell脚本这个有多种方法 最后还是选择了subprocess这个python标准库 subprocess这个模块可以非常方便的启动一个子进程,并且控制其输入和输出 Class Popen(args,bufsize = 0,executable=None, stdin =None,stdout =None,stderr =None, preexec_fn = None,close_fds =

  • 通过实例解析python subprocess模块原理及用法

    一.subprocess以及常用的封装函数 运行python的时候,我们都是在创建并运行一个进程.像Linux进程那样,一个进程可以fork一个子进程,并让这个子进程exec另外一个程序.在Python中,我们通过标准库中的subprocess包来fork一个子进程,并运行一个外部的程序. subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用.另外subprocess还提供了一些管理标准流(standard stream)

  • python subprocess pipe 实时输出日志的操作

    * test11.py import time print "1" time.sleep(2) print "1" time.sleep(2) print "1" time.sleep(2) print "1" * test.py import subprocess p = subprocess.Popen("python test11.py", shell=True, stdout=subprocess.

  • python中的subprocess.Popen()使用详解

    从python2.4版本开始,可以用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值. subprocess意在替代其他几个老的模块或者函数,比如:os.system os.spawn* os.popen* popen2.* commands.* 一.subprocess.Popen subprocess模块定义了一个类: Popen class subprocess.Popen( args, bufsize=0, executable

  • Python中判断subprocess调起的shell命令是否结束

    前言 最近在使用subprocess遇到个问题,折腾了好半天才找到简单的解决办法,在这里记录下. 环境 Python:2.7.10 库:subprocess, logging 问题 使用subprocess的Popen类来执行shell命令,要怎么样才能知道命令执行结束了,以此来执行回调方法. 解决办法 使用subprocess.Popen.poll方法来获取命令的执行情况. poll方法的返回值有两种情况    1. 当命令未运行结束的时候,返回None    2. 当命令结束时,返回命令的返

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

    0x01 问题现象 写的程序使用subprocess创建子进程运行其他程序,判断其他程序运行完后进行处理. 在subprocess使用了shell=True,判断用户程序退出的代码如下 while self.proc.poll() is None: do_something 判断子进程是否运行结束,程序在子进程运行结束后,代码未向下继续运行,而是卡在了这个循环中. 0x02 原因分析 百度后对shell参数的解释如下: shell=True参数会让subprocess.Popen接受字符串类型的

  • 解决vant框架做H5时踩过的坑(下拉刷新、上拉加载等)

    1. 页面在手机端不能上下滑动,在PC端浏览器正常滑动 说明:在设置了overflow:auto;属性的前提下,H5页面在PC端浏览器里展示可以上下滑动,在ios上可正常滑动,在安卓手机 上不能上下滑动:这现象并不是ios和安卓兼容性问题! 原因:设置了touch-action: none;这属性为局部或者全局属性,将这条属性注释即可正常滑动. 2.使用PullRefresh和List列表实现下拉刷新和上拉加载时出现的问题 问题1. 下拉刷新时在手机上,不论滑到任何位置,只要下拉就刷新 原因:滑

  • 浅谈python配置与使用OpenCV踩的一些坑

    下载opencv2.4.9(python2.7匹配)后 (1)运行OpenCV 2.4.9.exe: (2)配置Python:将\opencv\build\python\2.7\x64 这个目录下:cv2.pyd 复制到:Python27\Lib\site-packages\目录下: (3)测试:输入import cv2,如报错,说明未安装成功 1.opencv的版本一定要与python的版本匹配,否则是python是无法调用cv2这个模块的. 错误信息:ImportError DLL load

  • jpa使用manyToOne(opntional=true)踩过的坑及解决

    目录 jpa使用manyToOne(opntional=true)踩坑 @ManyToOne用于一对多的情况 @manytoone设置为optional=true不起作用 @manytoone 原因 jpa使用manyToOne(opntional=true)踩坑 @ManyToOne用于一对多的情况 (默认情况下是懒加载的,没必要去配置哦)如:一个account可以对应多个accountPrivilege @Entity @Table(name = ACCOUNT_PRIVILEGE) pub

  • 解决Pytorch中Batch Normalization layer踩过的坑

    1. 注意momentum的定义 Pytorch中的BN层的动量平滑和常见的动量法计算方式是相反的,默认的momentum=0.1 BN层里的表达式为: 其中γ和β是可以学习的参数.在Pytorch中,BN层的类的参数有: CLASS torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) 每个参数具体含义参见文档,需要注意的是,affine定义了BN层的

  • 解决Django transaction进行事务管理踩过的坑

    概要 Transaction是django进行数据库原子性操作在python层面上的实现. 简单来说, 被transaction.atomic()包裹的代码块只在代码块顺利完成后进行数据库层面的commit.实际开发当中,遇到了一些问题. 1. transaction事务内不执行数据库的commit操作 除非手动commit transaction最基本的功能. 代码场景: 在事务当前启动celery异步任务, 无法获取未提交的改动. def example_view(request): wit

  • 解决python 执行shell命令无法获取返回值的问题

    问题背景:利用python获取服务器中supervisor状态信息时发现未能获取到返回值. python获取执行shell命令后返回值得几种方式: # 1.os模块 ret = os.popen("supervisorctl status") ret_data = ret.read() # 2.subprocess模块 ret = subprocess.Popen('supervisorctl status',shell=True,stdout=subprocess.PIPE) out

  • 解决python脚本中error: unrecognized arguments: True错误

    出现如图所示错误: 问题: 例如下述代码,给extract_features赋值True,出现上述错误. parser.add_argument('--extract_features', action='store_true') 解决: 简单来说,其实不用管,可以直接去掉赋值,认为其保存了一个布尔值,不是真的,就是假的.若设置了默认值是false,所以当然的就是false,但是如果不设置的话,store_true的意思就是默认存成真的. 补充知识:Anaconda3:conda-script.

随机推荐