subprocess模块主要用于创建子进程,并连接它们的输入、输出和错误管道,获取它们的返回状态。通俗地说就是通过这个模块,你可以在Python的代码里执行操作系统级别的命令,比如“ipconfig”、“du -sh”等等。subprocess模块替代了一些老的模块和函数,比如:`os.system,os.spawn,os.popen*`
subprocess.Popen()的基本入参介绍
subprocess.Popen()(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, encoding=None, errors=None)
功能:执行args参数所表示的命令,等待命令结束,并返回一个CompletedProcess类型对象。
注意,run()方法返回的不是我们想要的执行结果或相关信息,而是一个CompletedProcess类型对象。
上面参数表里展示的只是一些常用的,真实情况还有很多。
args:表示要执行的命令。必须是一个字符串,字符串参数列表。
stdin、stdout和stderr:子进程的标准输入、输出和错误。其值可以是subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者None。subprocess.PIPE表示为子进程创建新的管道。subprocess.DEVNULL表示使用os.devnull。默认使用的是None,表示什么都不做。另外,stderr可以合并到stdout里一起输出。
timeout:设置命令超时时间。如果命令执行时间超时,子进程将被杀死,并弹出TimeoutExpired异常。
check:如果该参数设置为True,并且进程退出状态码不是0,则弹出CalledProcessError异常。
encoding:如果指定了该参数,则stdin、stdout和stderr可以接收字符串数据,并以该编码方式编码。否则只接收bytes类型的数据。
shell:如果该参数为True,将通过操作系统的shell执行指定的命令
。
subprocess.Popen()交互式输入
import subprocess
# 进入shell
s = subprocess.Popen("adb shell ", stdout=subprocess.PIPE, stdin=subprocess.PIPE)
# 再次输入指令ps -A
s.stdin.write(b"ps -A")
# 注意一定要关闭输入,反否则无法进行读取
s.stdin.close()
# 输出每行的数据
for i in s.stdout.readlines():
print(i)
以上需要注意的是使用s.stdin.write()之后一定要关闭输入,否则会无法进行读取
实时打印手机日志
ti = time.time() + 60
os.popen(f"adb -s {device_id} shell logcat -c")
time.sleep(2)
# 开始抓取日志
ps = subprocess.Popen("adb -s {} shell logcat -v threadtime".format(device_id),stdout=subprocess.PIPE,stdin=subprocess.PIPE)
time.sleep(1)
# 启动app
d.app_start(package_name)
# time.sleep(1)
if (package_name=="com.vmos.pro"):
d(resourceId="com.vmos.pro:id/iv_vm_action").click()
while True:
string=ps.stdout.readline().decode("gbk", "ignore")
# print(string)
if (yemian1 in string) :
d11 = re.search("[0-9]{2}-[0-9]{2} ([0-9]{2}:){2}[0-9]{2}.[0-9]{3}",string)[0]
d1 = datetime.datetime.strptime(f'{d11}', '%m-%d %H:%M:%S.%f')
print(d1, "页面1启动")
elif (yemian2 in string):
d22 = re.search("[0-9]{2}-[0-9]{2} ([0-9]{2}:){2}[0-9]{2}.[0-9]{3}",string)[0]
d2 = datetime.datetime.strptime(f'{d22}', '%m-%d %H:%M:%S.%f')
print(d2, "页面2启动")
break
elif ti<time.time():
print("启动超时")
# 假如是因为广告超时则点击广告后重试
if (package_name == "com.f1player") or (package_name == "com.x8zs.sandbox"):
if d.xpath(
'//*[@resource-id="com.byted.pangle:id/tt_reward_full_frame_top"]/android.widget.FrameLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]').wait(
0.5):
print("出现广告")
d(text="| 跳过").click(timeout=30)
d(resourceId="com.byted.pangle:id/tt_video_ad_close_layout").click()
ps.kill()
d.app_stop(package_name)
assert 0
ps.kill()
# os.system('taskkill /t /f /pid {}'.format(ps.pid))
# 计算时间
d3 = d2 - d1
# 打印时间 取小数点后2位并转换位浮点数
d4 = (float(str(d3)[5:10]))
print(d4)
return d4
以上是根据日志获取app启动时间的计算,这里值得注意的是,每次获取到跳出循环之后一定要把把创建的进程给杀掉(比如上面使用了ps.kill()),不然后台会一直残留这个进程,但是假如subprocess.Popen(shell=Ture)的话使用常规的kill()方法是无法杀掉的,因为使用了 shell=True,结束 process 之后,logcat子进程并不会被结束。所以可以使用 os.system(‘taskkill /t /f /pid {}’.format(ps.pid)) 的方式来杀掉子进程