查看: 265|回复: 3

Python调用Linux命令subprocess用法与报错排查

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
Python 开发者写脚本,最终运行环境大多是 Linux 服务器。熟悉常用命令并不是为了背命令表,而是为了部署、看日志、排查进程,以及在 Python 里正确调用系统命令完成自动化。本文围绕高频 Linux 命令、subprocess 调用方式和常见报错,整理一套可直接落地的实践思路。

一、先理解命令行为什么绕不开

在 Windows 上写 Python 代码,语法可以兼容,但路径分隔符、换行符、依赖安装、文件权限都可能不同。部署到 Linux 后,登录服务器、上传文件、运行脚本基本都要通过命令行完成。自动化脚本的本质,也往往是把一串命令行操作固化成代码,例如批量重命名对应 mv,定时抓取对应 curl 加 grep。脚本出错后看日志,更离不开 tail、grep、less。

二、高频命令的使用要点

1. 目录与路径

pwd 用来确认当前工作目录;cd 负责切换目录,既可用绝对路径 /home/user/project,也可用相对路径 cd ..、cd ../..、cd project;~ 表示当前用户家目录。ls 用于列目录,ls -l 看权限、属主、大小和修改时间,ls -a 看隐藏文件,ls -lh 以人类可读单位显示大小。部分发行版有 ll 别名,但服务器上不一定存在,稳妥写法仍是 ls -l。

2. 文件操作

touch 创建空文件或更新时间戳;cp 复制文件,复制目录必须加 -r,否则会报 cp: omitting directory;mv 用于移动或重命名,同目录内 mv old.txt new.txt 就是重命名;rm 删除文件,删目录加 -r,加 -f 强制不确认。rm -rf 必须谨慎,执行前先停一下确认路径,必要时先用 ls 确认目标是否为要删除的内容。

3. 文本查看与检索

cat 适合查看小文件,大文件用 less 翻页并按 q 退出;head -n 20 看前 20 行,tail -n 50 看最后 50 行,tail -f 持续跟踪新增内容。grep 是文本检索核心,grep ERROR app.log 可筛出包含 ERROR 的行,-i 忽略大小写,-n 显示行号,-v 反向匹配,-E 支持正则。管道组合很实用:
  1. tail -f app.log | grep ERROR
复制代码

统计不同错误类型出现次数时,可结合 sort 和 uniq:
  1. grep ERROR app.log | sort | uniq -c | sort -rn
复制代码

wc -l 可快速统计行数,用来评估日志规模。

4. 权限与身份

ls -l 第一列的 10 个字符描述权限,如 -rw-r--r--,第一个字符表示文件类型,后面每三位分别对应属主、属组、其他人。chmod 755 file 是常见可执行文件权限,chmod 644 file 适合普通数据文件。chown user:group file 修改属主和属组。whoami 查看当前用户,id 查看用户 ID 和所属组;需要管理员权限时使用 sudo,例如 sudo apt install nginx,但不建议所有命令都挂 sudo。

5. 进程与资源排查

ps -ef 或 ps aux 可列出进程,包含 PID、CPU、内存和命令;ps -ef | grep python 可确认 Python 程序是否在运行,结果中出现 grep 自身进程属于管道机制的常见现象。top 动态查看资源占用,按 q 退出;free -h 看内存;df -h 看磁盘。出现 No space left on device 时先跑 df -h 确认磁盘。结束进程用 kill PID,僵死进程可用 kill -9 PID,注意后面跟的是 PID 而不是程序名。

三、Python 中调用 Linux 命令

os.system() 只能拿到退出状态码,拿不到命令输出,不适合需要解析结果的脚本。推荐使用 subprocess:
  1. import os
  2. import subprocess
  3. # 不推荐:拿不到输出,参数处理容易出问题
  4. os.system('ls -l')
  5. # 推荐:能捕获输出,还能拿到退出码
  6. result = subprocess.run(['ls', '-l'], capture_output=True, text=True)
  7. print(result.stdout)
  8. print(result.returncode)
复制代码

subprocess.run 接收列表,第一个元素是命令名,后续是参数;capture_output=True 捕获标准输出和标准错误,text=True 按文本模式解码而不是返回字节串。使用管道等 Shell 特性时可加 shell=True:
  1. result = subprocess.run('ls -l | grep py', shell=True, capture_output=True, text=True)
复制代码

但命令里拼接外部输入时不能使用 shell=True,例如 subprocess.run(f'rm -rf {user_input}', shell=True) 存在注入风险。正确做法是用列表传参,让系统处理转义。

四、三个可直接复用的脚本

1. 统计日志中的 ERROR 数量:
  1. import subprocess
  2. result = subprocess.run(
  3.     ['grep', '-c', 'ERROR', '/var/log/app.log'],
  4.     capture_output=True, text=True
  5. )
  6. count = int(result.stdout.strip())
  7. print(f'当前日志中的ERROR总数: {count}')
复制代码

日志路径固定时,这类脚本配合 cron 可变成简单异常监控。

2. 检查磁盘使用率,超过 80% 告警:
  1. import subprocess
  2. result = subprocess.run(['df', '-h', '/'], capture_output=True, text=True)
  3. lines = result.stdout.strip().splitlines()
  4. header = lines[0].split()
  5. data = lines[1].split()
  6. usage = data[4].rstrip('%')
  7. print(f'磁盘总量: {data[1]},已用: {data[2]},使用率: {usage}%')
  8. if int(usage) > 80:
  9.     print('磁盘使用率超过80%,需要清理')
  10. else:
  11.     print('磁盘状况正常')
复制代码

脚本从 df -h 输出第 5 列提取使用率,真实项目可继续接入邮件或即时通讯告警。

3. 批量给 .log 文件添加后缀:
  1. import subprocess
  2. result = subprocess.run(['find', '.', '-name', '*.log'], capture_output=True, text=True)
  3. for path in result.stdout.strip().splitlines():
  4.     new_path = path + '.bak'
  5.     subprocess.run(['mv', path, new_path])
  6.     print(f'已重命名: {path} -> {new_path}')
复制代码

用 find 找文件,再用 mv 重命名,逻辑比手写纯 Python 遍历更短,前提是路径中不含特殊字符。

五、常见报错与排查流程

command not found 通常有三类原因:命令未安装、命令不在当前用户 PATH 中、拼写错误。可用 which python3 查看命令路径;刚安装完命令找不到时,先新开终端,因为配置可能只在重新登录或新 Shell 时加载。

文件明明存在,脚本里相对路径却找不到,通常是当前工作目录不一致。可在脚本开头切换目录:
  1. import os
  2. os.chdir(os.path.dirname(os.path.abspath(__file__)))
复制代码

之后再用相对路径就不容易迷路。

中文乱码要分两层排查。终端层可检查并设置 UTF-8:
  1. export LANG=C.UTF-8
复制代码

该设置对当前终端进程有效,需要永久生效应写入用户配置。Python 读写文件时要显式声明编码,例如读取 GBK 日志:
  1. open('log.txt', encoding='gbk', errors='ignore')
复制代码

Windows 的 CRLF 与 Linux 的 LF 不一致,也会导致脚本出现隐蔽语法错误。可用 sed 去掉行尾回车:
  1. sed -i 's/\r$//' script.py
复制代码

更彻底的方式是在编辑器中统一使用 LF。

启动服务报 Address already in use 时,先查端口占用:ss -tlnp | grep 8080 或 lsof -i :8080,找到 PID 后再决定是否 kill。若进程杀不掉,先 kill,等几秒再考虑 kill -9,同时不要误杀不认识的系统进程。

日常效率方面,Tab 补全能自动补全命令、文件名和路径,降低打错字概率;方向键上翻历史命令,history 查看记录,Ctrl+R 反向搜索。man 命令名 可查手册,按 q 退出。

六、练习与习惯

不要背命令清单,改用场景驱动:第一天创建目录并复制文件,用到 mkdir -p、cp -r、mv;第二天查看服务是否报错,用到 tail -f、grep、less;第三天排查磁盘,用到 df -h、du -sh、find。真正需要形成肌肉记忆的是目录操作、文件操作、文本检索三类。凡是超过三步的重复操作,都可以写成脚本放进 tools/ 目录,这既是命令行能力的延伸,也是 Python 自动化思维的体现。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python调用Linux命令subprocess用法与报错排查

这篇整理得挺系统的,从常用命令到 subprocess 调用再到脚本示例,基本把日常服务器上会碰到的场景串起来了。几个点特别认同:列表传参比字符串拼命令靠谱,shell=True 遇到外部输入的确是个坑,rm -rf 前先 ls 确认目标也很有必要,这些细节容易在新手脚本里出问题。 补充两个实践中常踩的小坑,供参考。一是 grep -c 在没有任何匹配时返回码是 1,不是 0,如果脚本里只看 returncode 或者没判断 stdout,容易把“没有 ERROR”误判成执行失败,最好把 returncode 和 stdout 分开处理。二是解析 df -h 的列时,不同发行版或不同挂载类型下列数可能不完全一致,用 split 取第 5 列在多数环境没问题,但更稳妥的是先匹配表头或者干脆用 df -P 的固定格式,避免以后换机器出意外。 另外帖子最后第三段脚本好像断在 `resul` 那里了,批量加后缀的部分没贴完,方便的话可以补全,那个例子涉及循环和 mv 的调用,对新手挺有参考价值的。整体思路很清晰,cron 加脚本做轻量监控这个方向也很实用。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python调用Linux命令subprocess用法与报错排查

写得很实在,把命令行为和部署、排查场景绑在一起讲,比单纯列命令表有用多了。几个小地方我踩过坑,补充一下供参考。 subprocess.run 默认不检查返回码,脚本一里 grep -c 在没匹配到时退出码是 1,但 stdout 还是 0,所以 int 转换没问题;可要是日志文件路径写错或没权限,grep 退出码是 2,stdout 是空串,int('') 直接就抛异常了。所以这类脚本最好显式看一下 returncode,或者先判断 stdout.strip() 是否为空,别默认命令一定成功。 磁盘那个脚本,df -h 在设备名特别长的时候会折行输出,lines[1] 就不一定是数据行了,取值会错位。换成 df -hP 或者 df -BG 固定格式会稳一些;也可以直接解析 df 的 --output=pcent 输出,省去按列拆分。另外使用率那里如果有多个挂载点,最好确认取的是根分区那一行。 shell=True 的风险讲得对,补充一点:需要管道特性时也可以不用 shell=True,自己把两个 run 串起来,或者用 shlex.split,可控性更好。 另外 text=True 在 Python 3.6 上不认,要写 universal_newlines=True;服务器上如果日志里混了非 UTF-8 字符,加个 errors='replace' 能避免解码直接炸掉。tail
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python调用Linux命令subprocess用法与报错排查

这篇总结挺实用的,subprocess.run 那部分讲得比较到位,列表传参加 capture_output 避免 shell 注入,确实是实际写脚本时最容易踩的坑。补充几个我自己踩过的点,供参考。 一是 run 里最好加上 check=True 和 timeout。很多命令失败时 returncode 非零但不会抛异常,脚本会继续往下跑,反而更危险;timeout 能防止某条命令挂住把定时任务堆死。 二是 text=True 依赖系统 locale,服务器上 LANG 没配好时,中文或特殊字符解码可能直接抛 UnicodeDecodeError,改成 encoding 指定 utf-8 加 errors 参数会更稳一些。 三是 grep -c 那个例子,没有匹配时 grep 返回码是 1,但 stdout 仍然是“0”,所以 int 转换没问题。不过如果想区分“文件不存在”和“0 条 ERROR”,还是得看 returncode,否则路径写错也会被当成正常结果。 四是 df -h 解析那段,文件系统名太长时 df 会折行,lines[1] 就不一定是根分区那行了;挂载点带空格也会让 split 错位。用 df -P 或者 df 的 output 参数指定只取使用率那一列,格式更固定,解析起来省心很多。 五是 kill -9 那里,可以补一句先普通 kill 发 SIGTERM 给
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-13 07:49 , Processed in 0.021098 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部