1. 为什么Python程序员绕不开Linux
先聊个真实的场景。我见过不少Python开发者,在Windows上写代码写得飞起,一到部署就抓瞎。本地跑得好好的Django项目,传到服务器上不是缺依赖就是路径不对,折腾一晚上还是没跑起来。说句不客气的话,问题多半出在你不熟悉Linux。
Python这门语言有一个特点——它的生态和Linux深度绑定。从包管理到进程管理,从日志查看到定时任务,生产环境里几乎清一色是Linux。哪怕你用的是Docker,底层跑的也是Linux内核。你本地用的Windows只是开发环境,真正的"战场"在Linux上。
所以很多团队招Python后端工程师的时候,都会带上一条"熟悉Linux常用命令"。这不是门槛,是基本功。说白了,Linux命令对你的意义不是"会不会敲",而是"能不能在遇到问题的时候,第一时间找到关键线索"。
这篇文章不是让你背命令手册,而是从一个Python开发者的实际角度,把日常开发、调试、部署、排错过程中真正用得上的命令和思路梳理一遍。文中的命令我都按"什么场景下用、为什么这么用、有没有坑"来写,照着敲就能用。
2. 环境隔离:虚拟环境与Python版本管理
2.1 venv:Python项目的第一道隔离墙
Python项目最头疼的问题之一就是依赖冲突。项目A要用Django 3.2,项目B要升到Django 5.0,如果你只有一个全局Python环境,早晚要出事。虚拟环境就是干这个的。
创建虚拟环境的标准姿势:
# 进入你的项目目录 cd ~/projects/myblog # 创建虚拟环境,名字叫 venv(也有人用 .venv,看团队习惯) python3 -m venv venv # 激活虚拟环境 source venv/bin/activate激活之后你会注意到命令行前面多了个(venv)前缀,这就说明当前shell环境已经切换到这个项目专属的Python环境里了。之后再敲pip install,装的包只会出现在这个虚拟环境里,不会污染全局。
退出虚拟环境用:
deactivate有一个细节很多新手会忽略:激活虚拟环境之后,命令行里的python和pip指的都是venv/bin/下的软链接,不再是系统的/usr/bin/python3。你可以用which python查看路径确认一下:
which python # 输出类似:~/projects/myblog/venv/bin/python2.2 用requirements.txt锁定依赖
虚拟环境搭配requirements.txt是Python项目的标配。生成依赖清单:
# 导出当前环境的所有依赖及精确版本号 pip freeze > requirements.txt在另一台机器上还原环境:
pip install -r requirements.txt实际工作中,我强烈建议你区别对待requirements.txt和requirements-dev.txt。前者只放生产环境需要的依赖(Django、gunicorn、redis这些),后者额外放开发工具(pytest、black、flake8)。否则部署的时候会装一堆没用的包,浪费时间和磁盘空间。
2.3 pyenv:管理多个Python版本
有时候你手上不止一个项目,有的用Python 3.8,有的用Python 3.12。光靠系统自带的Python版本是没法满足的。这时候用pyenv。
安装pyenv的步骤这里不展开,装好之后核心命令很好记:
# 查看可安装的Python版本 pyenv install --list # 安装指定版本 pyenv install 3.10.14 # 设置当前目录使用的Python版本(会在目录下生成 .python-version 文件) pyenv local 3.10.14 # 查看当前使用的版本 pyenv versionspyenv的原理不复杂:它把不同版本的Python编译后装到~/.pyenv/versions/目录下,再通过PATH环境变量的顺序切换,让当前shell优先使用指定版本的Python。相当于给"用哪个Python解释器"加了一层代理。
我自己的习惯是:系统Python永远不动,全局用pyenv指定一个稳定版本,每个项目再用venv隔离依赖,三层互不干扰。这套组合拳在本地开发和服务器部署上都适用。
3. 日志、文件和文本处理:Python程序员的日常操作
3.1 查看日志的姿势比你想的重要
程序跑起来之后,第一件事就是看日志。日志就是程序的"体检报告",哪行代码出了什么问题、请求耗时多少、有没有报错,都在里面。
最基础的查看命令:
# 查看日志文件完整内容 cat app.log # 分页查看,适合大文件 less app.log # 只看最后200行,部署后最常用 tail -n 200 app.log # 实时跟踪日志输出,Ctrl+C退出 tail -f app.log部署场景里,我90%的时候都在用tail -f。比如你用gunicorn跑Django服务,启动之后窗口上刷的日志就是标准输出,但如果你用nohup或systemd把服务放后台跑,日志就落到了文件里。这时候tail -f就能实时看到请求进来了、有没有报500。
3.2 日志检索:grep才是主角
日志文件几十MB甚至上百MB的时候,用cat或less翻页完全没有效率。正确做法是用grep检索关键字:
# 查找所有包含 Traceback 的行 grep "Traceback" app.log # 查找ERROR级别的日志,并显示行号 grep -n "ERROR" app.log # 不区分大小写,且显示匹配行的前后各5行 grep -i -n -C 5 "timeout" app.log-C 5这个参数特别有用。报错信息往往不在一行里,比如Python的异常堆栈,Traceback (most recent call last)在顶部,真正的原因可能隔了十几行。用-C把上下文带出来,才能看清前因后果。
如果你要在大量日志里统计某个关键字出现的次数:
grep -c "ERROR" app.log # 或者忽略大小写 grep -ci "error" app.log3.3 用awk和sed做轻量级数据处理
Python程序员可能觉得:处理文本我直接用Python不就行了?当然可以,但在Linux命令行环境下,awk和sed有时候更顺手,尤其是快速看一眼数据时,没必要为了一个小需求单独写脚本。
awk的核心逻辑是"按列处理"。日志里最常见的格式是空格或逗号分隔的字段。比如一条Nginx日志:
127.0.0.1 - - [10/Oct/2024:13:55:36 +0800] "GET /api/users HTTP/1.1" 200 1024我想只看IP、请求路径和状态码:
awk '{print $1, $7, $9}' access.logawk默认按空格切分,$1是IP,$7是请求路径,$9是状态码。就这么简单。
sed的核心逻辑是"按行替换",最常用的场景是批量替换配置或数据里的内容:
# 把 import 后面的 requests 改成 httpx,忽略大小写 sed -i 's/requests/httpx/g' requirements.txt # 只打印第10到20行 sed -n '10,20p' app.log3.4 查找文件:find和locate
项目文件越堆越多,经常忘了某个配置文件放哪了。这时候:
# 在当前目录下递归查找所有 .py 文件 find . -name "*.py" # 按文件名查找,并排除venv目录 find . -name "settings.py" -not -path "./venv/*" # 查找最近10分钟内修改过的文件 find . -mmin -10 # 按文件大小查找(大于50MB的文件) find . -size +50Mfind和grep还经常组合在一起用,比如查找所有包含requests的Python文件:
grep -rl "requests" --include="*.py" .-r是递归,-l是只列出文件名。
另外,locate命令用数据库索引查文件名,速度比find快一个数量级。缺点是索引不是实时的,新文件可能查不到,需要先执行updatedb更新索引。
3.5 磁盘和文件大小排查
日志文件过多会把磁盘塞满,这是服务器上最常见的故障之一。排查思路:
# 查看磁盘整体使用情况 df -h # 查看当前目录下各个子目录和文件的占用大小 du -sh * # 按大小排序,找到最大的目录 du -sh * | sort -rh | head -n 10df -h看的是挂载点的空间,du -sh *看的是实际文件占用。组合使用能快速定位"磁盘满"的元凶。我遇到过几次生产事故,最后都是查du -sh *发现是日志文件没做轮转,一直长到几十GB。
4. Python项目部署里的关键环节
4.1 用systemd管理Python服务
开发环境下你可能习惯python manage.py runserver,但生产环境这么跑等于裸奔——终端一关,服务就没了。正确的做法是把服务交给systemd管理,开机自启、崩溃自动重启、日志统一收集。
先创建一个service文件:
sudo vim /etc/systemd/system/myblog.service内容大致长这样:
[Unit] Description=My Django Blog After=network.target [Service] User=deploy Group=deploy WorkingDirectory=/home/deploy/projects/myblog ExecStart=/home/deploy/projects/myblog/venv/bin/gunicorn config.wsgi:application -b 127.0.0.1:8000 --workers 3 Restart=always RestartSec=3 [Install] WantedBy=multi-user.target注意ExecStart里我写的是venv/bin/gunicorn而不是全局的gunicorn。这一步非常关键,因为在venv里安装的gunicorn只有venv自己知道,不写绝对路径的话,systemd是在系统环境里找命令,大概率会报404找不到。
写完配置之后的操作:
# 重新加载systemd配置 sudo systemctl daemon-reload # 启动服务 sudo systemctl start myblog # 设置开机自启 sudo systemctl enable myblog # 查看服务状态 sudo systemctl status myblog # 实时查看服务日志 sudo journalctl -u myblog -fjournalctl这个命令建议你刻意多用几次,它会把你服务的标准输出和标准错误统一收集起来。以后排查问题的时候,不需要登录到服务器上一个文件一个文件找,直接一条命令全看。
4.2 后台运行与持久化日志
有些临时脚本不想用systemd托管,比如一个跑数据采集的任务,你想让它安安静静在后台跑,关掉终端也不中断。这时候用nohup:
nohup python3 spider.py > spider.log 2>&1 &拆开看是什么意思:
nohup忽略挂断信号,终端关闭进程不退出>把标准输出写到 spider.log2>&1把标准错误也写到同一个文件,这步千万别省,不然报错你看不到- 最后的
&是让进程进入后台
之后想看任务跑得怎么样:
# 查看进程是否存活 ps aux | grep spider.py # 看日志尾部 tail -f spider.log # 杀掉进程 kill <PID>4.3 定时任务:crontab
数据清洗、报表统计、定时备份,这些重复性工作交给crontab最合适。
# 编辑当前用户的定时任务 crontab -e一行一个任务,格式是"分 时 日 月 周 命令"。想每天凌晨2点执行一次备份脚本:
0 2 * * * /home/deploy/projects/backup.sh >> /home/deploy/backup.log 2>&1这里又一个常见坑:cron环境下的PATH和你在终端里看到的不一样,基本是最小环境。所以脚本里尽量写命令的绝对路径,或者干脆在脚本开头先source ~/.bashrc。不然你会发现,手动执行好好的脚本,进了cron就报"command not found"。
定时任务的日志一般放在系统日志里:
# 查看当前用户的cron执行日志 grep CRON /var/log/syslog4.4 端口占用与进程排查
服务起不来,第一反应不是看代码,而是看端口有没有被占。比如8000端口被别的进程占了:
# 查看谁占用了8000端口(新版Linux用ss更方便) ss -tlnp | grep 8000 # 老一点的系统也可以用netstat netstat -tlnp | grep 8000-t是tcp,-l是监听状态,-n不显示域名直接显示IP,-p显示进程名和PID。看到PID之后直接:
# 杀掉占用端口的进程 kill -9 <PID>查进程本身:
# 查看所有Python进程 ps aux | grep python # 只看进程树,理清父子关系 pstree -ppstree这个命令在排查"进程怎么又多了几个"或者"谁启动了谁"的时候非常直观。比如你用gunicorn起了3个worker,pstree能清晰地看到master进程下面挂着3个child进程。
4.5 系统资源查看:top和free
线上服务变慢,先看两类资源:CPU和内存。
# 动态查看系统进程资源占用,按CPU排序 top # 按内存排序 top -o %MEM # 实时刷新时按M键,也能切换成按内存排序内存方面:
# 查看内存使用情况 free -hfree -h里有几个指标要注意:available才是真正可用的内存,不是free列。因为Linux会用空闲内存做文件缓存(buff/cache),这部分在需要时是可以释放的。你要是拿free列来判断内存够不够,很容易误判成"内存不足"。
Python项目常见的内存问题有两个:一是进程积压太多请求没释放,二是某个长驻进程慢慢涨。这时候用top观察RES列的变化,看哪个进程内存持续上涨,再结合日志定位代码位置。
5. 远程连接与文件传输:从本地开发到服务器部署
5.1 SSH:远程开发的地基
到服务器上操作,最常用的工具就是SSH。
# 连接服务器 ssh deploy@your_server_ip # 指定端口连接 ssh -p 2222 deploy@your_server_ip # 执行单条命令,不进入交互界面 ssh deploy@your_server_ip "df -h"SSH的认证方式,生产环境强烈建议用密钥而不是密码。生成密钥对:
ssh-keygen -t rsa -b 4096生成的公钥在~/.ssh/id_rsa.pub,私钥在~/.ssh/id_rsa。把公钥内容追加到服务器上~/.ssh/authorized_keys文件里,之后登录就不用输密码了。
更省事的方式是用ssh-copy-id直接把公钥推过去:
ssh-copy-id deploy@your_server_ip5.2 scp与rsync:文件怎么传到服务器
本地文件传到服务器,最简单的是scp:
# 上传本地文件到服务器 scp local_file.py deploy@your_server_ip:/home/deploy/ # 上传整个目录,加 -r scp -r ./myblog deploy@your_server_ip:/home/deploy/ # 从服务器下载文件到本地 scp deploy@your_server_ip:/home/deploy/app.log ./scp够用,但如果你经常同步代码目录,推荐rsync,它的增量同步比scp全量复制高效得多:
# 同步代码目录到服务器,排除venv和缓存文件 rsync -avz --exclude 'venv' --exclude '__pycache__' ./myblog/ deploy@your_server_ip:/home/deploy/myblog/-a是归档模式,保文件属性;-v是显示过程;-z是压缩传输,局域网或带宽有限时效果明显。--exclude可以忽略不需要同步的目录,比如venv、.git、pycache、.log文件。
rsync 还有一点对开发者特别实用:如果本地和服务器文件一模一样,它会直接跳过,不会白白占带宽。本地改了几个文件就只传那几个,省时省力。
5.3 tmux:远程会话不断线
SSH登录到服务器干活,最怕的是网络一抖,会话断开,正在跑的任务也没了。tmux就是一个会话保持工具,让你的任务在远程终端关闭之后继续跑。
# 新建一个会话 tmux new -s work # 脱离会话(回到普通shell,任务继续在后台跑) Ctrl+b 然后按 d # 查看所有会话 tmux ls # 重新接入之前的会话 tmux attach -t work整个流程就是:SSH登录服务器,tmux new -s deploy,在里面跑部署、跑脚本,然后Ctrl+b d断开。回家关电脑,第二天登录服务器,tmux attach -t deploy,任务还在那儿等你。
我部署Django项目、跑数据迁移这类耗时操作,几乎都在tmux里做。哪怕服务器SSH断了也不慌,重新连上再接回去就行。
5.4 快速排查网络问题
部署完之后,服务没起来或者访问不了,按这个顺序排查:
# 1. 先看端口有没有监听 ss -tlnp | grep 8000 # 2. 本机访问试试 curl -I http://127.0.0.1:8000 # 3. 看对外IP和网关 ip addr ip route # 4. 测试外网访问是否通 ping -c 4 8.8.8.8curl是检验HTTP服务最方便的工具,curl -I只看响应头,curl -v显示详细交互过程。后端接口返回500还是404,用curl一眼就能看出来。
6. 基础但高频的命令:文件操作与权限
6.1 文件目录的基本操作
# 查看当前目录 pwd # 列出文件(-l 详细信息,-a 含隐藏文件,-h 人类可读大小) ls -lah # 创建目录(-p 递归创建) mkdir -p data/logs # 复制文件(-r 复制目录) cp -r project_backup project_new # 移动或重命名 mv old_name.py new_name.py # 删除文件(-rf 递归强制删除目录,慎用) rm -rf temp_dirrm -rf是Linux里最容易出事故的命令,没有之一。我的经验是:删除重要东西之前先ls确认路径,或者在命令里用绝对路径再核对一遍。尤其不要随手在rm -rf后面加空格和*,手一抖整个目录就没了。
6.2 权限管理:chmod和chown
Python项目部署到服务器后,经常遇到"权限不够"或者"不能执行"的问题。权限的基础知识是:每个文件有三组权限,分别针对所有者、所属组、其他人。每组权限用字母或数字表示,rwx对应4、2、1。
# 给脚本添加执行权限 chmod +x run.sh # 设置所有者读、写、执行(7),组读执行(5),其他人读执行(5) chmod 755 run.sh # 修改文件所有者为deploy用户 sudo chown deploy:deploy app.log一个常见的场景:你从本地rsync文件到服务器,所有者是root,结果部署用户是deploy,程序启动时没权限写日志文件,报PermissionError。解决办法就是chown把目录归属改过来:
sudo chown -R deploy:deploy /home/deploy/myblog/6.3 环境变量:让程序能找到配置
Python程序里经常需要读取环境变量,比如数据库密码、SECRET_KEY。在Linux下设置环境变量:
# 当前会话临时生效 export DATABASE_URL="postgres://user:pass@localhost/dbname" python3 app.py # 永久生效,把export写入 ~/.bashrc 或 ~/.profile echo 'export DATABASE_URL="postgres://user:pass@localhost/dbname"' >> ~/.bashrc source ~/.bashrc # 查看某个环境变量 echo $DATABASE_URL在systemd的service文件里,也可以用Environment字段配置环境变量。这个比往.bashrc里写更干净,服务级别的配置就应该跟服务走。
7. 一些能直接提升效率的命令组合
7.1 alias:把长命令变成快捷键
有些命令组合你每天都在敲,完全可以收进~/.bashrc里:
alias activate='source venv/bin/activate' alias gs='git status' alias gd='git diff' alias gl='git log --oneline --graph --decorate -n 20' alias ll='ls -lah'改完source ~/.bashrc生效。我习惯把git的常用操作也建一套alias,省下来的时间积少成多。
7.2 history:找到你曾经敲过的命令
命令记不住没关系,shell会替你记。
# 查看历史命令 history # 搜索历史命令 history | grep "gunicorn" # 直接执行历史记录中的第100条 !100还有一个技巧:终端里按Ctrl+r进入反向搜索,输入关键字,会自动提示最近匹配的命令。比history | grep更顺手。
7.3 组合使用:管道符的威力
Linux命令的哲学是一个工具只干一件事,多个工具通过管道串联成一条流水线。
# 查看当前目录下最大的10个文件 find . -type f -exec du -h {} + | sort -rh | head -n 10 # 统计日志中各个IP的访问次数 awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -n 10这两个例子分别用了find+du+sort+head,以及awk+sort+uniq+sort+head。单独看每个命令都很简单,但串起来就能在几十秒内完成原本要写几十行Python脚本才能做的数据分析。
7.4 一键重跑部署任务的思路
部署流程重复且机械,适合写成一个Shell脚本。比如我常用的一个更新脚本:
#!/bin/bash set -e echo ">> pull code" git pull origin main echo ">> install dependencies" source venv/bin/activate pip install -r requirements.txt echo ">> migrate" python manage.py migrate --noinput echo ">> collect static" python manage.py collectstatic --noinput echo ">> restart service" sudo systemctl restart myblog echo ">> done"set -e表示任何一条命令失败就立即退出,避免在错误状态下继续跑后续步骤。脚本写好后:
chmod +x deploy.sh ./deploy.sh把重复劳动脚本化,是初级开发到高级开发的一个重要分水岭。
7.5 开发中的一个小技巧:Python -m http.server
有时候你想临时起一个简单的HTTP服务,让人下载文件或者测试页面:
# 在当前目录启动一个HTTP服务,默认端口8000 python3 -m http.server 8000这个命令对Python开发者来说等于内置了一个轻量的静态文件服务器,调试前端代码、临时分享文件都能用。而且它不需要任何第三方库,Python自带。
8. 一些实用的排查工具和思路
8.1 strace:黑盒排查利器
程序跑不起来,或者卡住不动,日志又什么都没打。这时候可以用strace跟踪系统调用,看程序在干什么:
# 跟踪某个进程的系统调用 sudo strace -p 12345 # 跟踪程序启动时的系统调用,保存到文件 sudo strace -o trace.log python3 app.pystrace能看到程序打开哪些文件、访问哪些网络端口、报了什么系统级别的错误。比如一个常见的场景:Python程序启动时报Permission denied,但日志里的错误信息很模糊,用strace就能看到它到底是哪个文件的权限不够。这是"找不到日志原因"时的终极大杀器。
8.2 lsof:文件和端口的对应关系
lsof能列出进程打开的文件和网络端口:
# 查看某个文件被哪个进程占用 lsof app.log # 查看某个用户打开的所有文件 lsof -u deploy # 查看某个端口被哪个进程监听 lsof -i :8000有人说ss已经能看端口占用,为什么还要lsof?因为lsof的视角更偏文件,比如你想知道"这个日志文件被谁一直占用导致磁盘删不掉",用lsof就能快速定位。
8.3 快速定位CPU占用高的Python代码
如果你发现线上某个Python服务CPU飙高,可以这样快速定位:
# 1. 找到CPU占用最高的进程 top -o %CPU # 2. 把进程转成线程号 # 记下PID,然后用 top -Hp 看线程 top -Hp <PID> # 3. 用py-spy直接采样Python调用栈(重点) sudo py-spy dump --pid <PID>py-spy是一个专门针对Python进程的采样工具,不需要重启服务就能看到当前Python代码执行到了哪一行。比用gdb折腾半天高效多了。
安装方式:
pip install py-spy8.4 查看服务运行时间的uptime
uptimeuptime会显示系统运行时间、当前登录用户数、以及1/5/15分钟的平均负载。如果负载持续很高,说明系统处于过载状态,需要结合top看是CPU-bound还是内存不足导致的swap频繁。
9. 最后再聊点实在的
坦白说,Linux命令这个东西,光看不练一点用都没有。你把这篇文章里的命令挨个敲一遍,比背十遍命令手册都管用。我自己也是从"一个命令在一个项目里被坑了三次才记住"的状态过来的,踩坑不可怕,关键是每次踩坑之后能把对应的命令和思路沉淀下来。
我个人的体会是,不必追求把所有命令都背得滚瓜烂熟,你只需要记住每个场景下"有哪些工具可以用",具体参数忘了就man或者--help。比如grep的参数那么多,常用的就是-n -i -C,其他参数真要用了现查也不迟。
还有一个建议是尽量养成在Linux环境下开发的好习惯,不是让你完全抛弃IDE和图形界面,而是说写代码、跑测试、看日志这些操作,能到命令行做的就到命令行做。这样你会发现,真正上服务器部署排错的时候,一切都很自然,不会有一种"我是在另一个世界干活"的割裂感。
工具是死的,思路是活的。把这些命令串起来,形成一套属于自己的排查路径,比记住任何单个命令都重要。