pyinstaller打包成无控制台程序时运行出错(与popen冲突的解决方法)
作者:PurePeace 发布时间:2023-05-15 02:03:20
有时候我们需要在程序里执行一些cmd命令,使用os或者其它模块中的popen方法去执行
这个问题一般是程序内有输入导致的,这个输入可以是input()
,也可以是其它的一些stdin操作(如os.popen实际上会造成输入请求)
本质上就是:使用-w
参数(无控制台)打包时程序里不要请求输入
或者,你也可以不用-w
参数,手动隐藏控制台!
有一天,我把使用了os.popen方法的python程序用pyinstaller打包成exe(用了无控制台打包参数-w
)
双击运行时程序却弹框报错!
我就有点纳闷:为什么有控制台打包出来的exe(不使用-w
参数)可以运行,使用-w
参数(无控制台)打包的却不能运行呢?
首先,调用os.popen
部分的代码大概是下面这样的:
with os.popen('taskkill /f /t /im nginx.exe') as re: # 杀掉nginx
result = re.read()
执行cmd,杀死nginx。
经过研究,上结论:
os.popen
会打开一个管道执行命令,而管道是有输入(stdin)、输出(stdout) 的!
重点就在输入(stdin)这里:
当我们使用pyinstaller的-w
参数(或Console=False
)打包exe时,python解释器是不带控制台的,
所以它没有办法处理输入(stdin) !
包括使用python的input()
函数也是不行的,都会弹框报错。
那么怎么办呢?接着看!
os.popen
实际上是一个简单的封装,我们先来看他的原型:subprocess.popen
subprocess.Popen(
args,
bufsize=0,
executable=None,
stdin=None,
stdout=None,
stderr=None,
preexec_fn=None,
close_fds=False,
shell=False,
cwd=None,
env=None,
universal_newlines=False,
startupinfo=None,
creationflags=0
)
简单的解释一下(详细请看官方文档):
subprocess官方文档:https://docs.python.org/2/library/subprocess.html
懒得看解释可以直接跳过下面这段,直接看解决方法
args
是一个字符串(如cmd命令),或者是包含程序参数的列表。要执行的程序一般就是这个列表的第一项,或者是字符串本身。但是也可以用executable
参数来明确指出。当executable
参数不为空时,args
里的第一项被认为是“命令名”,不同于真正的可执行文件的文件名,这个“命令名”是一个用来显示的名称,例如执行unix/linux下的 ps
命令,显示出来的就是这个“命令名”。
bufsize
作用就跟python函数open()
的buffering
参数一样:0表示不缓冲,1表示行缓冲,其他正数表示近似的缓冲区字节数,负数表示使用系统默认值。默认是0。
executable
参数指定要执行的程序。它很少会被用到,一般程序可以由args参数指定。如果shell
参数为True
,executable可以用于指定用哪个shell来执行(比如bash、csh、zsh等)。windows下,只有当你要执行的命令是shell内建命令(比如dir
,copy
等) 时,你才需要指定shell=True
,而当你要执行一个基于命令行的批处理脚本(bat啥的)的时候,不需要指定此项。
stdin
、stdout
和stderr
分别表示子程序的标准输入、标准输出和标准错误。 可选的值有PIPE
或者一个有效的文件描述符(其实是个正整数)或者一个文件对象,还有None。如果是PIPE
,则表示需要创建一个新的管道,如果是 None
,不会做任何重定向工作,子进程的文件描述符会继承父进程的。另外,stderr
的值还可以是STDOUT
,表示子进程的标准错误也输出到标准输出。
如果把preexec_fn
设置为一个可调用的对象(比如函数),就会在子进程被执行前被调用。(仅限unix/linux)
如果把close_fds
设置成True,unix/linux下会在开子进程前把除了0、1、2以外的文件描述符都先关闭。在 Windows下也不会继承其他文件描述符。
如果把shell
设置成True
,指定的命令会在shell
里解释执行,这个前面已经说得比较详细了。
如果cwd
(工作目录)不是None
,则会把cwd做为子程序的当前目录。注意,并不会把该目录做为可执行文件的搜索目录,所以不要把程序文件所在目录设置为cwd。
如果env
不是None
,则子程序的环境变量由env的值来设置,而不是默认那样继承父进程的环境变量。注意,即使你只在env里定义了某一个环境变量的值,也会阻止子程序得到其他的父进程的环境变量(也就是说,如果env里只有1项,那么子进程的环境变量就 只有1个了)。
如果把universal_newlines
设置成True
,则子进程的stdout
和stderr
被视为文本对象,并且不管是unix/linux的换行符('\n'),还是老mac格式的换行符('\r'),还是windows 格式的换行符('\r\n')都将被视为'\n' 。
如果指定了startupinfo
和creationflags
,它们将会被传递给后面的CreateProcess()
函数,用于指定子程序的各种其他属性,比如主窗口样式或者是子进程的优先级等。(仅限Windows)
再解释一下两个我们后面要用到的东西:
subprocess.PIPE
一个可以用于Popen的stdin
、stdout
或stderr
参数的特殊值,它指示应打开到标准流的管道。
subprocess.STDOUT
一个可以被用于Popen的stderr
参数的特殊值,表示子程序的标准错误与标准输出汇合到同一句柄。
现在回到我们将要解决的问题
已知:
用pyinstaller的
-w
参数打包导致python无法处理输入值(stdin)os.popen
打开的管道却需要处理输入值(stdin)
所以,我们不使用os.popen
这个简单的封装,改成使用subprocess.popen
,接着将subprocess.popen
打开管道的输入值(stdin)重定向,即可解决问题!
请看下列示例:
proc = subprocess.Popen(
'cmd命令',
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.PIPE # 重定向输入值
)
proc.stdin.close() # 既然没有命令行窗口,那就关闭输入
proc.wait()
result = proc.stdout.read() # 读取cmd执行的输出结果(是byte类型,需要decode)
proc.stdout.close()
这样处理后我们用-w
参数打包就不会再报错了!
也可以将输出值(stdout)定向到文件输出,请看:
with open('输出文件.txt' , 'w+', encoding='utf-8') as out_file:
proc = subprocess.Popen(
'cmd命令',
shell=True,
stdout=out_file, # 注意这里!变成了文件对象!
stderr=subprocess.STDOUT,
stdin=subprocess.PIPE
)
ret = proc.wait() # 此处其实有返回值
with open('输出文件.txt', 'r', encoding='utf-8' as read_file:
output = read_file.read() # 这样就得到cmd命令的输出结果了
稍微封装一下,就可以直接拿来用了
def execute_cmd(cmd):
proc = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.PIPE
)
proc.stdin.close()
proc.wait()
result = proc.stdout.read().decode('gbk') # 注意你电脑cmd的输出编码(中文是gbk)
proc.stdout.close()
return result
result = execute_cmd('taskkill /f /t /im nginx.exe')
print(result)
舒服了!!!!
当然,实在要用输入,又不想要控制台怎么办?很简单,把控制台隐藏了就行!
下列两个方法,试试看:
import ctypes
def hideConsole():
"""
Hides the console window in GUI mode. Necessary for frozen application, because
this application support both, command line processing AND GUI mode and theirfor
cannot be run via pythonw.exe.
"""
whnd = ctypes.windll.kernel32.GetConsoleWindow()
if whnd != 0:
ctypes.windll.user32.ShowWindow(whnd, 0)
# if you wanted to close the handles...
#ctypes.windll.kernel32.CloseHandle(whnd)
def showConsole():
"""Unhides console window"""
whnd = ctypes.windll.kernel32.GetConsoleWindow()
if whnd != 0:
ctypes.windll.user32.ShowWindow(whnd, 1)
来源:https://blog.csdn.net/qq_26373925/article/details/105521118


猜你喜欢
- //创建组件 function createobj() { if (window.ActiveXObject) { return(new A
- print函数的功能是打印图窗或保存为特定文件格式。语法print(filename,formattype)print(filename,f
- 需要注意的是,firefox下必须要设置下 signed.applets.codebase_principal_support 在 fire
- 一个功能的实现,可以用多种语句来实现,比如说:while语句、for语句、生成器、列表推导、内置函数等实现,然而他们的效率并不一样。写了一个
- MyBatis-Plus实现数据库curd操作1.mp是什么MyBatis-Plus(简称MP)是一个MyBatis 的增强工具,在MyBa
- 前言在程序中我们经常可以看到有很多的加密算法,比如说MD5 sha1等,今天我们就来了解下这下加密算法的吧,在了解之前我们需要知道一个模块嘛
- 动态引入store modules主要解决的问题每次建一个module需要自己去主index.js里面去注册为了偷懒,也为了避免团队开发时同
- 自己试过很好用function zero_fill_hex(num, digits) { var s = num.toString(16);
- 阅读上一篇文章《WEB2.0网页制作标准教程(5)head区的其他设置》在开始正式内容制作之前,我们必须先了解一下web标准有关代码的规范。
- 前言正则表达式是对字符串提取的一套规则,我们把这个规则用正则里面的特定语法表达出来,去匹配满足这个规则的字符串。正则表达式具有通用型,不仅p
- 前言这篇博文的目的是演示如何使用 OpenCV、Python 和面部标志对齐人脸。给定一组面部标志(输入坐标),我们的目标是将图像扭曲并转换
- folder.htm<html><head><title>闪亮日子之在线创建文件夹</title&
- mark 一下使用 bootstrap selectpicker 遇到的一个小issue,作为下次查错使用$('.selectpic
- 本文介绍使用Python进行Socket网络编程,假设读者已经具备了基本的网络编程知识和Python的基本语法知识,本文中的代码如果没有说明
- 怎么用javascript进行拖拽本文译自:http://www.webreference.com/programming/javascri
- 如下所示:import matplotlib.pyplot as plt import tkinter import numpy as np
- <%@ Language=VBScript %><HTML><HEAD>
- 今天遇到下图这种问题,文字过长,显示不全。折腾了老半天,在网上搜了半天也找不到解决方案。于是问了下同事,同事提到了<optgroup&
- 我就废话不多说了,直接上代码吧!import torchimport torch.nn.functional as Fimport nump
- python逆序的三位数程序每次读入一个正3位数,然后输出按位逆序的数字。注意:当输入的数字含有结尾的0时,输出不应带有前导的0。比如输入7