064、os 与 sys 模块:环境变量、命令行参数、文件系统操作实战集合

发布时间:2026/6/28 18:46:02
064、os 与 sys 模块:环境变量、命令行参数、文件系统操作实战集合 064、os 与 sys 模块环境变量、命令行参数、文件系统操作实战集合一个让我熬夜到凌晨三点的bug上周五晚上一个线上服务突然报错日志里只有一行“Permission denied”。我盯着屏幕看了两个小时最后发现是os.makedirs()的exist_ok参数没传——目录已经存在但代码里没处理这个情况导致整个部署脚本崩了。这种低级错误说出来都丢人。但更丢人的是我后来发现同事的代码里用sys.argv[1]取参数结果传参时忘了加引号路径里的空格把参数切成了两半。这两个模块看着简单坑是真的多。os模块别把它当玩具环境变量操作——生产环境最容易翻车的地方importos# 获取环境变量别直接抛异常# 这里踩过坑线上环境某个变量没设直接报KeyError服务挂了db_hostos.getenv(DB_HOST,localhost)# 给个默认值保命用db_portos.environ.get(DB_PORT,3306)# 两种写法都行但getenv更语义化# 设置环境变量——注意这只影响当前进程# 别这样写以为改了系统环境变量重启服务就没了os.environ[MY_APP_MODE]production# 遍历环境变量——调试时很有用forkey,valueinos.environ.items():ifPATHinkey:# 只看PATH相关的print(f{key}{value})血的教训有一次我在Dockerfile里用os.environ设置变量以为能持久化结果容器重启后全没了。正确做法是用export或者Docker的ENV指令。文件系统操作——路径拼接是门玄学importos# 路径拼接——永远不要用字符串加号# 别这样写base_path / filenameWindows上直接炸base_dir/home/user/projectconfig_pathos.path.join(base_dir,config,settings.json)# 获取当前文件所在目录——这个技巧我用了五年current_diros.path.dirname(os.path.abspath(__file__))# 或者用pathlibPython 3.4推荐frompathlibimportPath current_dirPath(__file__).parent# 创建目录——exist_ok救过我的命# 这里踩过坑多进程同时创建目录不加exist_ok直接抛FileExistsErroros.makedirs(/tmp/myapp/logs,exist_okTrue)# 目录存在也不报错# 判断文件/目录是否存在ifos.path.exists(/tmp/test.txt):print(文件存在)# 获取文件信息——调试时看时间戳file_statos.stat(/tmp/test.txt)print(f大小:{file_stat.st_size}bytes)print(f修改时间:{file_stat.st_mtime})# 时间戳需要转换路径拼接的坑我见过最离谱的代码用base_path / filename结果base_path末尾有斜杠变成了//filename。os.path.join会自动处理这些别自己造轮子。目录遍历——递归的优雅写法importos# 遍历目录——os.walk是神器# 别这样写自己写递归遍历又慢又容易栈溢出forroot,dirs,filesinos.walk(/home/user/project):forfileinfiles:iffile.endswith(.py):full_pathos.path.join(root,file)print(f找到Python文件:{full_path})# 这里可以加文件处理逻辑# 只遍历当前目录——os.listdir就够了foriteminos.listdir(.):ifos.path.isdir(item):print(f目录:{item})elifos.path.isfile(item):print(f文件:{item})性能提示os.walk默认会递归所有子目录如果目录层级很深比如node_modules记得用topdown参数控制遍历顺序或者用scandir替代listdir性能提升明显。sys模块命令行参数的坑命令行参数——空格是魔鬼importsys# 获取命令行参数——sys.argv[0]是脚本名# 这里踩过坑用户传参时没加引号路径里的空格把参数切碎了# 比如python script.py /home/user/my file.txt# 实际得到的是[script.py, /home/user/my, file.txt]script_namesys.argv[0]argssys.argv[1:]# 真正的参数# 安全获取参数——给个默认值iflen(sys.argv)1:input_filesys.argv[1]else:input_filedefault.txt# 更专业的做法——用argparseimportargparse parserargparse.ArgumentParser(description处理文件)parser.add_argument(--input,-i,requiredTrue,help输入文件路径)parser.add_argument(--output,-o,defaultoutput.txt,help输出文件路径)parser.add_argument(--verbose,-v,actionstore_true,help详细输出)argsparser.parse_args()print(f输入:{args.input})print(f输出:{args.output})ifargs.verbose:print(详细模式开启)为什么不用sys.argv直接解析因为用户永远会给你惊喜。空格、引号、转义字符手动解析这些会让你怀疑人生。argparse帮你处理了所有边界情况。系统路径——模块导入的暗坑importsys# 查看Python搜索路径forpathinsys.path:print(path)# 动态添加路径——临时救急用# 别这样写在正式代码里加这个说明你的项目结构有问题sys.path.insert(0,/home/user/my_custom_libs)# 更优雅的做法设置PYTHONPATH环境变量# export PYTHONPATH/home/user/my_custom_libs:$PYTHONPATH# 获取Python版本信息print(fPython版本:{sys.version})print(f版本号:{sys.version_info.major}.{sys.version_info.minor})# 退出程序——带状态码# 0表示正常退出非0表示异常sys.exit(0)# 正常退出sys.exit(1)# 异常退出脚本可以捕获这个状态码路径导入的坑有一次我改了项目结构忘了更新sys.path结果导入模块时一直报ModuleNotFoundError。后来发现是路径顺序问题——Python会按sys.path的顺序查找如果有个同名模块在更前面的路径里就会导入错误的版本。标准输入输出——重定向的骚操作importsys# 重定向输出到文件# 这里踩过坑忘记恢复sys.stdout导致后续所有print都写到文件里了original_stdoutsys.stdoutwithopen(output.log,w)asf:sys.stdoutfprint(这条信息会写入文件)sys.stdoutoriginal_stdout# 记得恢复# 更安全的做法用contextlibfromcontextlibimportredirect_stdoutwithopen(output.log,w)asf:withredirect_stdout(f):print(这条信息也会写入文件)# 退出with块后自动恢复# 读取标准输入——管道操作时很有用datasys.stdin.read()print(f从管道读取了{len(data)}个字符)调试技巧当你的脚本被cron调用时print输出不会显示在终端。用sys.stderr.write()可以确保错误信息被记录到日志里。实战案例一个完整的文件处理脚本#!/usr/bin/env python3# -*- coding: utf-8 -*-importosimportsysimportargparsefromdatetimeimportdatetimedefsetup_logging(log_dir):设置日志目录返回日志文件路径# 创建日志目录exist_okTrue防止重复创建报错os.makedirs(log_dir,exist_okTrue)# 生成带时间戳的日志文件名timestampdatetime.now().strftime(%Y%m%d_%H%M%S)log_fileos.path.join(log_dir,fprocess_{timestamp}.log)returnlog_filedefprocess_files(input_dir,output_dir,file_pattern):处理指定目录下的文件# 检查输入目录是否存在ifnotos.path.isdir(input_dir):print(f错误: 输入目录{input_dir}不存在,filesys.stderr)sys.exit(1)# 创建输出目录os.makedirs(output_dir,exist_okTrue)processed_count0# 遍历输入目录forroot,dirs,filesinos.walk(input_dir):forfileinfiles:# 检查文件扩展名iffile.endswith(file_pattern):input_pathos.path.join(root,file)output_pathos.path.join(output_dir,file)try:# 读取文件内容withopen(input_path,r,encodingutf-8)asf:contentf.read()# 这里可以加你的处理逻辑# 比如替换文本、转换格式等processed_contentcontent.upper()# 示例转大写# 写入输出文件withopen(output_path,w,encodingutf-8)asf:f.write(processed_content)processed_count1print(f处理完成:{input_path}-{output_path})exceptExceptionase:print(f处理文件{input_path}时出错:{e},filesys.stderr)returnprocessed_countdefmain():主函数parserargparse.ArgumentParser(description文件处理工具)parser.add_argument(--input,-i,requiredTrue,help输入目录)parser.add_argument(--output,-o,requiredTrue,help输出目录)parser.add_argument(--pattern,-p,default.txt,help文件扩展名过滤)parser.add_argument(--log-dir,-l,default./logs,help日志目录)argsparser.parse_args()# 设置日志log_filesetup_logging(args.log_dir)print(f日志文件:{log_file})# 处理文件countprocess_files(args.input,args.output,args.pattern)print(f总共处理了{count}个文件)if__name____main__:main()使用示例python file_processor.py-i/home/user/input-o/home/user/output-p.csv个人经验性建议路径操作永远用os.path.join别自己拼字符串。Windows和Linux的路径分隔符不一样os.path帮你处理了所有差异。环境变量读取一定要给默认值。线上环境变量没设的情况太常见了os.getenv(‘VAR’, ‘default’)能救你一命。sys.argv只适合最简单的脚本。一旦参数超过两个果断上argparse。用户传参的方式千奇百怪argparse帮你兜底。os.walk遍历大目录时注意性能。如果目录里有几十万个文件os.walk会卡死。考虑用os.scandir或者第三方库如pathlib。调试时多用sys.stderr。print输出到stdout容易被管道或重定向干扰。错误信息写到stderr日志系统能正确捕获。别在生产代码里动态修改sys.path。这说明你的项目结构有问题应该用PYTHONPATH环境变量或者包管理工具。文件操作记得加异常处理。磁盘满了、权限不够、文件被占用这些情况随时可能发生。try-except是最后的防线。用pathlib替代os.path。Python 3.4的pathlib提供了面向对象的路径操作代码更简洁但老项目里os.path还是主流。最后说一句这两个模块的文档我翻烂了但每次遇到新问题还是得回去查。别指望一次记住所有API记住常用的几个其他的知道去哪里查就行。