Python脚本脱离控制台后print()崩溃,如何检查stdout可写性?
print()崩溃,如何安全检查stdout可用性? 场景复现
- 用
script.py & disown -f或setsid -f script.py启动脚本,关闭控制台后脚本很快崩溃; - 用
nohup script.py &或at now执行脚本(输入script.py后按Ctrl+D),脚本可正常运行。
脚本逻辑为每10秒休眠后执行处理,通过print()输出内容。排查确认崩溃原因是print()语句,注释后脚本稳定运行。改用logging模块替换print()解决了问题:
logging.basicConfig(format='%(message)s', encoding='utf-8', level=logging.INFO,handlers=[logging.StreamHandler(sys.stdout)])
将print()替换为logging.info()即可正常运行。
已知前两种启动方式会让stdout处于关闭/无效状态,但Python受影响而bash脚本不受;at now方式会将输出发送至邮件,因此stdout未关闭。
核心疑问
如何检查是否可安全使用print()或sys.stdout.write()写入stdout?尝试过以下两个判断条件,但脚本仍会在关闭控制台后调用print()导致崩溃:
if sys.stdout.closed == False: if sys.stdout is not None:
测试代码:
#!/usr/bin/python3 import time import sys for i in range(1,100): # 两种判断结果一致 # if sys.stdout.closed == False: if sys.stdout is not None: print(str(i)) time.sleep(10)
解决方案
1. 捕获写入异常
直接检查sys.stdout.closed或sys.stdout is not None并不够——控制台关闭后,stdout的文件描述符可能未被显式关闭,但已指向无效终端(即"broken pipe")。此时写入会触发BrokenPipeError(Python 3.3+)或IOError,正确做法是捕获这类异常:
#!/usr/bin/python3 import time import sys for i in range(1,100): try: print(str(i)) sys.stdout.flush() # 强制刷新缓冲区,避免异常延迟触发 except (BrokenPipeError, IOError): sys.stdout.close() # 关闭stdout,避免后续重复触发异常 # 也可根据需求选择退出脚本:sys.exit(1) time.sleep(10)
2. 提前验证stdout有效性
可以通过os.isatty()检查stdout是否指向有效终端,或尝试写入空字节测试可用性:
import os import sys def is_stdout_writable(): if sys.stdout.closed: return False # 检查是否为有效终端 try: if os.isatty(sys.stdout.fileno()): return True except OSError: pass # 尝试写入空字节测试 try: sys.stdout.write('') sys.stdout.flush() return True except (BrokenPipeError, IOError): return False
调用示例:
for i in range(1,100): if is_stdout_writable(): print(str(i)) time.sleep(10)
3. 沿用logging模块
logging模块已内置处理stdout不可写的逻辑,输出失败时会自动跳过或处理异常,无需手动判断,是最省心的方案。
原因说明
控制台关闭后,前两种启动方式下的Python脚本,其stdout对应的文件描述符虽未被显式关闭,但已指向不存在的终端设备(管道破裂)。此时sys.stdout.closed仍为False,sys.stdout也不为None,但写入操作会触发管道破裂异常导致脚本崩溃;而bash脚本默认忽略这类错误,因此不会崩溃。
内容的提问来源于stack exchange,提问作者razvan

