在Linux命令行中运行Python脚本的流程步骤
发布时间:2026/10/11 13:31:04 锦皓数字建站

前言把写好的.py文件在 Linux 命令行上跑起来是运维与开发里最常见的一步但这一步能踩的坑比想象中多脚本明明在终端能跑放进计划任务就报「找不到模块」输出全混在一起分不清是正常结果还是报错脚本失败了但流程还以为它成功了。两个常见误解先澄清。第一以为脚本必须加可执行权限、必须写 shebang 才能运行——不是的python3 script.py这种方式完全不需要 shebang也不需要chmod xshebang 与可执行权限只在你打算把脚本当命令直接执行时才需要。第二以为「能跑就行」不管标准输出与标准错误的分流、不看退出码——这恰恰是脚本进不了自动化流程的主要原因。本文按顺序讲清六件事三种执行方式的差别、模块方式的特殊语义、路径与PYTHONPATH、输出重定向与退出码、以及把脚本放进 cron 时必然遇到的三个环境陷阱。必须说明本机没有 Python 解释器本文没有运行过任何命令所有命令的用法与语义均按 Python 官方文档与 Linux 手册页写出。一、三种执行方式方式命令形态需要 shebang需要可执行权限典型场景直接指定脚本python3 script.py否否开发调试、临时执行当命令执行./script.py是是部署成工具、放进 PATH当模块执行python3 -m 包.模块否否包内相对导入、给库用方式一python3 script.py。最直接。注意在多数发行版上python这个名字可能根本不存在或者指向的版本和你预期的不一样显式写python3或者写清具体版本更稳妥。要传参数给脚本脚本名之后的内容会进sys.argv。方式二把它当成一条命令。官方文档在「在 Unix 平台上使用 Python」一节里写得很明确要让脚本在 Unix 上方便地使用需要让它可执行例如chmod x script并在脚本顶部放一行合适的 Shebang通常的好选择是#!/usr/bin/env python3它会沿着整条PATH去找解释器。文档同时提醒有些 Unix 系统可能没有env命令那就得把解释器路径硬编码为/usr/bin/python3。Shebang 有两个必须记住的细节它必须是文件的第一行前面不能有空行或注释并且它只在「把文件当命令执行」时起作用——当你写python3 script.py时shebang 只是一行普通注释。方式三python3 -m 模块名。官方文档的定义是按标准导入机制定位模块并把它的内容当作__main__模块执行因为参数是模块名不能带.py后缀包名含命名空间包也允许。这个方式看似绕但在两类场景里是必需的下面单独说。模块方式为什么有时是必需的关键在于解释器把哪个目录放进了sys.path的第一位以及包内的相对导入怎么解析。用python3 script.py时被放进搜索路径前面的是脚本所在目录。用python3 -m 包.模块时被放进前面的是当前工作目录。差别看起来小后果却很实际包内使用相对导入的模块几乎只能用-m方式跑。如果你直接python3 mypackage/worker.py那个文件是被当成顶层脚本执行的它的「包身份」丢了相对导入就会失败。这也是很多项目在文档里写「用python3 -m mypackage.worker启动」的原因。命令行还有一个与这条规则直接相关的开关-P3.11 起表示不要把可能有风险的路径前置到sys.path。文档给出的三种情形是-m命令行不前置当前工作目录script.py命令行不前置脚本所在目录-c与交互式不前置代表当前目录的空字符串。它对应环境变量PYTHONSAFEPATH。此外-E会忽略所有PYTHON*开头的环境变量-I是更彻底的隔离模式。二、路径与 PYTHONPATH当你的代码不在「脚本目录」或「当前目录」下时有三种办法让它被找到安装成包推荐。把代码做成可安装的包装进环境的site-packages之后从任何目录都能导入。用PYTHONPATH环境变量。它是一个目录列表效果相当于往sys.path里追加目录。写法与PATH类似多个目录用冒号分隔。在脚本里操作sys.path。可行但不推荐因为它把「怎么找到代码」这件事藏在了代码里别人从命令行完全看不出来。PYTHONPATH的正确用法是只加必要的顶层目录然后按包名导入。常见的错误是把包内部的目录也加进去导致同一个模块被当成两个不同的模块导入出现「类型不相等」「单例不唯一」这类诡异现象。# 让 /srv/app 成为可导入的顶层目录注意要 export 给子进程export PYTHONPATH/srv/apppython3 -c import mypkg; print(mypkg.__file__)# 只在这一次调用里生效不污染当前 shellPYTHONPATH/srv/app python3 -m mypkg.cli --help# 查看解释器实际的搜索路径python3 -c import sys; print(\n.join(sys.path))三、标准输出、标准错误与退出码命令行程序有两个输出流语义不同标准输出stdout用于「正常结果」标准错误stderr用于「诊断信息」。很多脚本把日志打到 stdout结果一旦被重定向日志就混进了要交给下游处理的数据里。判断依据很简单如果这段文字是给「人」看的它就该去 stderr。# 把正常结果写进文件诊断信息仍然显示在终端python3 app.py result.txt# 把诊断信息单独收集起来python3 app.py 2 error.log# 两个流都写进同一个文件顺序与缓冲有关不保证严格交错python3 app.py all.log 21# 两个流都不要python3 app.py /dev/null 2121的位置很关键 all.log 21表示「先把 stdout 指向文件再把 stderr 指向 stdout 现在指向的地方」写成21 all.log就变成了「先把 stderr 指向终端再改 stdout」效果完全不同。退出码是自动化流程判断成败的唯一依据。在 shell 里上一条命令的退出状态存在$?里0 表示成功非零表示失败。Python 侧由sys.exit()决定它。官方文档的原话是sys.exit([arg])抛出SystemExit异常表示要退出解释器可选参数arg可以是给出退出状态的整数默认为零也可以是其它类型的对象整数时零被视为「成功终止」任何非零值被 shell 之类视为「异常终止」文档还提醒多数系统要求它落在 0 到 127 范围内超出范围的后果是未定义的。# 适用于 Python 3.8用退出码把结果传递给调用方import sysdef main(argv):if len(argv) 2:# 用法错误打印到标准错误并返回非零退出码print(用法python3 app.py 目标路径, filesys.stderr)return 2target argv[1]print(f处理 {target})return 0if __name__ __main__:# sys.exit 会把返回值作为进程退出状态0 表示成功sys.exit(main(sys.argv))注意这里把sys.exit放在__main__保护块里并且错误信息明确写到sys.stderr——这两点合起来才能让调用方可靠地区分「正常输出」与「失败提示」。另外要记住未捕获的异常会让解释器以非零码退出所以「捕获所有异常然后正常返回」其实是把失败伪装成成功这在自动化里非常危险。四、放进 cron 运行把脚本交给 cron 定时执行时会遇到三个必然出现的环境差异。cron 不会继承你交互式登录时的环境这一点在 crontab 的手册页里有明确说明cron 守护进程会自动设置的环境变量包括SHELL被设为/bin/sh、LOGNAME与HOME后两者取自/etc/passwd中该用户的记录HOME与SHELL可以在 crontab 里覆盖LOGNAME不能。由此推出三个坑PATH不要指望它。手册只承诺了上面那几个变量PATH一类是你自己在 crontab 里设置的。你平时在~/.bashrc里追加的路径对 cron 任务无效因为任务用的是/bin/sh且是非交互、非登录方式启动。不要依赖当前工作目录。任务不会自动切到你期望的项目目录脚本里所有路径都用绝对路径或者在脚本开头显式切目录。没有终端TTY。任何等待键盘输入、检测终端宽度、输出彩色控制符的行为都会出问题或产生垃圾字符。还有两个 cron 特有的细节值得记住命令里的百分号%在未转义时会被转换成换行符第一个%之后的所有内容会作为标准输入送给命令手册原文如此所以命令里出现%必须写成\%。另外任务产生的输出如果没被重定向cron 会尝试把它寄给 crontab 的所有者MAILTO变量控制收件地址设为空串则不发信——这解释了为什么「莫名收到一堆邮件」通常意味着某个定时任务在往标准输出上写东西。# crontab 里的写法示意crontab -e 编辑# 路径显式声明命令用绝对路径输出自己收集PATH/usr/local/bin:/usr/bin:/binMAILTO30 2 * * * /usr/local/bin/python3 /srv/app/nightly.py /var/log/nightly.log 21# 要判断结果就在包装脚本里检查退出码0 3 * * * /srv/app/run.sh#!/bin/sh# run.sh把退出码显式检查一遍失败时打出可查的日志set -ucd /srv/app || exit 1/usr/local/bin/python3 nightly.py /var/log/nightly.log 21status$?if [ $status -ne 0 ]; thenecho nightly.py 失败退出码 $status /var/log/nightly.errfiexit $status常见坑点❌ 在脚本里用相对路径读配置文件手动运行正常放进 cron 就报文件不存在。 ✅ 一律用绝对路径或在脚本开头显式切换到项目目录别依赖调用方的工作目录。❌ 以为 cron 会读你的~/.bashrc因此直接在 crontab 里写python3 xxx.py。 ✅ cron 用的环境极简PATH要自己设解释器写绝对路径最稳。❌ 把错误信息也print到标准输出重定向后和正常数据混在一起。 ✅ 诊断信息写sys.stderr正常结果写标准输出让下游能按流区分。❌ 脚本里捕获了所有异常后返回 0让调用方以为一切正常。 ✅ 失败要有非零退出码退出码是自动化流程判断成败的唯一依据。❌ 写21 all.log以为和 all.log 21是一回事。 ✅ 重定向从左到右生效要合并两个流就写 all.log 21。❌ 直接执行包内的某个模块文件例如python3 mypackage/worker.py。 ✅ 包内相对导入要用python3 -m mypackage.worker执行否则「包身份」会丢失。❌ 在 crontab 的命令里直接写带%的参数。 ✅%在 crontab 里是特殊字符要写成\%或者把逻辑挪进包装脚本里。❌ 脚本发布后忘了chmod x或者 shebang 没写在第一行却直接./script.py。 ✅ 要当命令执行就必须「第一行是 shebang」且「有可执行权限」否则老老实实写python3 script.py。总结场景推荐做法临时运行python3 script.py当命令运行第一行 shebang chmod x包内模块python3 -m 包.模块模块找不到安装成包或用PYTHONPATH只加顶层目录输出分流结果走 stdout诊断走 stderr判断成败看退出码0 成功、非零失败放进 cron绝对路径、显式 PATH、自己重定向日志把脚本从「在终端里能跑」变成「在自动化里可靠地跑」靠的就是这三件事路径写绝对、输出分流、退出码说真话。cron 的陷阱并不神秘——它只是不给你任何交互式登录时的便利凡是依赖那些便利的写法都会在无人值守时暴露出来。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。