Python 系列(四十五) 使用 sys 模块处理标准输入与输出
1 | 作者:李晓辉 |
- Python 系列(一) 从零开始认识 Python,入门其实没那么难
- Python 系列(二) 学 Python 的第一步,把开发环境搭建好
- Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
- Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
- Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
- Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
- Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
- Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
- Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
- Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
- Python 系列(十一) list、tuple、set、dict,到底该怎么选?
- Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
- Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
- Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
- Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
- Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
- Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
- Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
- Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
- Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
- Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
- Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
- Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
- Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
- Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
- Python 系列(二十六) Python 模块入门,从 import 到模块复用
- Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
- Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
- Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
- Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
- Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
- Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
- Python 系列(三十三) 什么是类和对象?零基础理解 Python class
- Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
- Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
- Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
- Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
- Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
- Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
- Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
- Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
- Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
- Python 系列(四十三) 别只会处理异常!学会自定义异常,让程序更容易维护
- Python 系列(四十四) 从零认识 assert 断言,让程序学会检查自己
- Python 系列(四十五) 使用 sys 模块处理标准输入与输出
- Python 系列(四十六) 使用 open() 读取和写入文件
- Python 系列(四十七) 学会读写文本文件,让程序保存运行结果
- Python 系列(四十八) 搞懂 bytes、bytearray 和二进制读写
- Python 系列(四十九) 使用 seek() 和 tell() 灵活控制文件读取位置
- Python 系列(五十) 掌握 os 和 os.path,实现文件与目录自动化管理
- Python 系列(五十一) 掌握列表推导式,轻松实现列表生成与数据筛选
- Python 系列(五十二) 掌握字典推导式,让字典创建更简单
- Python 系列(五十三) 从零理解生成器,轻松处理大量数据
- Python 系列(五十五) 使用 zip() 同时遍历多个集合
前面我们学习了如何使用 input() 接收用户输入,以及使用 print() 在屏幕上显示内容。
例如,编写一个简单的程序:
1 | name = input("请输入你的姓名:") |
运行程序后,Python 会显示提示信息,等待我们输入姓名,然后输出问候语。
对于大多数简单的程序来说,input() 和 print() 已经足够了。
但是,如果我们希望更直接地控制程序的输入和输出呢?
例如:
- 从标准输入读取数据,而不是只依赖
input()。 - 将内容写入标准输出,并自行控制换行。
- 将错误信息单独输出,方便后续排查问题或收集日志。
- 将 Linux 命令生成的数据传递给 Python 程序,进行批量处理。
这时,我们就可以使用 Python 提供的 sys 模块。
认识 sys 模块
sys 是 Python 标准库中的一个模块,它提供了一些与 Python 解释器及其运行环境交互的功能。
可以把模块简单理解为一个已经准备好的工具箱。需要使用其中的工具时,我们先通过 import 导入模块,再调用对应的功能。
例如:
1 | import sys |
这里,import sys 表示导入 sys 模块,sys.version 用于查看当前 Python 解释器的版本信息。
在本篇中,我们重点学习 sys 提供的三个标准 I/O 对象:
sys.stdin:标准输入,通常用于读取输入数据。sys.stdout:标准输出,通常用于显示程序的正常输出。sys.stderr:标准错误,通常用于输出错误信息和诊断信息。
这里的 I/O 是 Input/Output 的缩写,意思是输入和输出。
理解标准输入、标准输出和标准错误
在 Linux 中,我们经常通过终端运行命令。执行命令时,程序需要接收数据,也需要向外输出执行结果。
Python 程序同样如此。
默认情况下,可以把这三个对象理解为三条不同的数据通道:
flowchart TD
A["用户或其他程序"] --> B["标准输入 stdin"]
B --> C["Python 程序"]
C --> D["标准输出 stdout"]
C --> E["标准错误 stderr"]
D --> F["正常执行结果"]
E --> G["错误与诊断信息"]例如,我们执行一个服务器巡检程序:
- 程序通过标准输入接收需要检查的服务器名称。
- 程序通过标准输出显示服务器的检查结果。
- 如果发生错误,程序可以通过标准错误输出错误信息。
需要注意,标准输出和标准错误默认都可能显示在终端上。它们的区别主要在于用途,以及可以被操作系统分别重定向和处理,而不是一个显示、另一个不显示。
使用 sys.stdout 输出内容
先看一个简单的例子。
创建文件 stdout_demo.py,写入以下代码:
1 | import sys |
运行程序:
1 | python3 stdout_demo.py |
预期输出:
1 | Hello, Python! |
这段代码中:
import sys:导入sys模块。sys.stdout:获取标准输出对象。write():向标准输出写入字符串。\n:表示换行。
这里有一个需要特别注意的区别:sys.stdout.write() 不会自动在字符串末尾添加换行符。
例如:
1 | import sys |
输出结果为:
1 | HelloPython |
因为两次写入的内容之间没有换行符,所以它们会直接连接在一起。
如果改成:
1 | import sys |
输出结果就会变成:
1 | Hello |
那么,sys.stdout.write() 和之前学过的 print() 有什么区别呢?
1 | print("Hello") |
默认情况下,print() 会在输出内容后添加换行符。
而:
1 | import sys |
只会写入指定的字符串,不会自动添加换行符。
此外,sys.stdout.write() 在正常写入字符串的情况下,会返回实际写入的字符数。不过,刚开始学习时,我们只需要先掌握它的基本输出功能即可。
对于普通的屏幕输出,优先使用 print() 通常更方便;当我们需要更直接地控制输出内容时,可以考虑使用 sys.stdout.write()。
使用 sys.stdin 读取输入
除了输出内容,我们还可以通过 sys.stdin 读取标准输入。
创建文件 stdin_demo.py:
1 | import sys |
运行程序:
1 | python3 stdin_demo.py |
在提示信息后输入 web01,再按回车,输出示例为:
1 | 请输入服务器名称:web01 |
我们逐步解释这段代码。
显示提示信息
1 | print("请输入服务器名称:", end="") |
这里使用了 print() 的 end 参数。
默认情况下,print() 会在输出内容后添加换行符。设置 end="" 后,输出结束时就不会自动换行,因此输入光标会停留在提示文字后面。
读取用户输入
1 | server_name = sys.stdin.readline() |
sys.stdin.readline() 会从标准输入读取一行内容。
如果输入 web01 并按下回车,读取到的字符串通常是:
1 | 'web01\n' |
这里的 \n 是用户按下回车后产生的换行符。对于以换行符结束的输入行,它通常会包含在读取结果中。
清理输入内容
1 | server_name.strip() |
strip() 可以去除字符串开头和结尾的空白字符,包括换行符。
因此,程序输出的服务器名称是 web01,而不是末尾还带着换行符的字符串。
需要注意,sys.stdin.readline() 与 input() 都可以读取用户输入,但它们的行为并不完全相同:
input()通常会移除读取内容末尾的换行符,并返回字符串。sys.stdin.readline()通常会保留读取内容末尾的换行符,因此经常需要进一步处理。
如果只是编写简单的交互程序,使用 input() 更直观;如果需要逐行读取标准输入,例如处理管道传入的数据,sys.stdin 就会很有用。
区分交互式输入与批量输入
前面我们学习了如何使用 input() 和 sys.stdin.readline() 读取输入。
但在实际使用中,我们还需要考虑一个问题:
程序是需要与用户进行交互,还是需要一次性处理一批数据?
这两种场景看起来相似,实际上的使用方式却有很大区别。
什么是交互式输入
交互式输入是指程序运行过程中,需要用户主动提供数据,程序才能继续处理。
例如,我们编写一个服务器检查程序,希望用户输入服务器名称,然后显示检查结果。
创建文件 interactive_input.py:
1 | server_name = input("请输入服务器名称:") |
运行程序:
1 | python3 interactive_input.py |
此时,程序会显示:
1 | 请输入服务器名称: |
程序会等待用户输入服务器名称。只有输入 web01 并按下回车后,程序才会继续执行。
1 | 请输入服务器名称:web01 |
整个过程可以理解为:
flowchart TD
A["启动 Python 程序"] --> B["显示输入提示"]
B --> C["等待用户输入"]
C --> D["用户输入 web01 并按回车"]
D --> E["程序获得服务器名称"]
E --> F["执行后续处理"]在这个过程中,用户和程序之间存在明显的交互:程序提出要求,用户提供数据,程序再根据数据执行后续操作。
这种方式适合以下场景:
- 让管理员输入需要检查的服务器名称。
- 让用户输入查询条件。
- 让用户选择操作菜单。
- 在执行某项操作前,让用户输入必要的参数。
对于这类场景,input() 通常是最简单、最直观的选择。
什么是批量输入
假设我们需要检查的不是一台服务器,而是 200 台服务器。
如果继续使用交互式输入,就可能需要反复输入服务器名称。这不仅效率低,也不适合自动化任务。
更合理的方式是:提前准备好服务器列表,让程序从文件或其他数据源接收这些内容,再自动逐条处理。
例如,创建服务器列表文件 servers.txt:
1 | web01 |
然后编写程序 batch_input.py:
1 | import sys |
运行程序:
1 | python3 batch_input.py < servers.txt |
预期输出:
1 | 准备检查服务器:web01 |
这里没有要求管理员逐个输入服务器名称。Python 直接从标准输入读取文件内容,每读取一行,就处理一台服务器。
整个过程可以理解为:
flowchart TD
A["准备 servers.txt"] --> B["通过输入重定向传入文件"]
B --> C["Python 从 sys.stdin 读取"]
C --> D["逐行获取服务器名称"]
D --> E{"是否为空行?"}
E -->|是| F["跳过当前行"]
E -->|否| G["处理服务器"]
F --> D
G --> D
D --> H["所有输入读取完毕"]这种方式适合处理服务器清单、日志记录、批量任务参数,以及其他由文件或 Linux 命令生成的数据。
交互式输入与批量输入有什么区别
| 对比项 | 交互式输入 | 批量输入 |
|---|---|---|
| 主要目的 | 与用户进行交互 | 自动处理一批数据 |
| 数据来源 | 通常是用户键盘输入 | 文件、管道或其他程序 |
| 常用方式 | input() | for line in sys.stdin |
| 程序行为 | 通常等待用户逐次提供数据 | 按输入内容逐条处理 |
| 常见场景 | 输入服务器名称、选择菜单 | 批量巡检、处理日志、自动化任务 |
需要注意,这并不意味着 input() 只能用于交互式输入,也不意味着 sys.stdin 只能用于批量输入。
实际上,input() 本身也是从标准输入读取数据。在终端中运行时,它通常表现为交互式输入;当标准输入被文件或管道替代时,它也可以读取预先准备好的数据。
真正的区别在于数据从哪里来,以及程序打算怎样处理这些数据。
另外,批量输入不等于一次性将全部数据加载到内存中。使用 for line in sys.stdin 时,程序通常逐行读取和处理数据,因此也适合处理较大的输入文件。
使用 sys.stdin 读取 Linux 管道传入的数据
前面我们使用 sys.stdin.readline() 读取了用户在终端中输入的一行内容,也学习了如何通过输入重定向读取文件。
但在 Linux 中,标准输入并不一定来自键盘,也可以来自其他程序。
例如,我们执行下面的命令:
1 | printf 'web01\nweb02\nweb03\n' | python3 batch_input.py |
这里出现了一个新的符号:|,它叫作管道符。
管道可以把前一个命令的标准输出,连接到后一个命令的标准输入。
flowchart TD
A["printf 输出服务器列表"] --> B["前一个命令的标准输出"]
B --> C["Linux 管道符 |"]
C --> D["Python 程序的标准输入"]
D --> E["for line in sys.stdin"]
E --> F["逐行读取服务器名称"]
F --> G["处理并输出结果"]在这个例子中,printf 命令输出三行服务器名称,管道将这些内容传递给 Python 程序。Python 不需要等待用户在键盘上输入,就可以直接读取数据。
理解逐行读取的过程
回到 batch_input.py 中的核心代码:
1 | for line in sys.stdin: |
其中:
1 | for line in sys.stdin: |
表示从标准输入中逐行读取数据。
当输入中有多行内容时,Python 会逐行读取,每读取一行,就执行一次循环中的代码。
接下来:
1 | server_name = line.strip() |
去除当前行首尾的空白字符,方便后续处理。
然后:
1 | if not server_name: |
判断清理后的服务器名称是否为空。如果为空,就使用 continue 跳过本次循环,继续处理下一行。
最后:
1 | print(f"准备检查服务器:{server_name}") |
输出当前读取到的服务器名称。
当标准输入中的内容全部读取完毕后,循环就会结束。
通过管道传入多台服务器
执行以下命令:
1 | printf 'web01\nweb02\nweb03\n' | python3 batch_input.py |
预期输出:
1 | 准备检查服务器:web01 |
其中,printf 负责生成三行内容,\n 表示换行符;管道符 | 将这些内容传递给 Python 程序的标准输入。
整个过程可以理解为:
flowchart TD
A["printf 生成服务器列表"] --> B["Linux 管道"]
B --> C["Python 标准输入 sys.stdin"]
C --> D["for 循环逐行读取"]
D --> E["strip() 清理内容"]
E --> F{"服务器名称是否为空?"}
F -->|是| G["跳过当前行"]
F -->|否| H["输出服务器名称"]
G --> D
H --> D
D --> I["输入读取结束"]注意,这次程序没有等待我们在键盘上输入服务器名称,而是直接处理管道传入的三行数据。
从文件中读取服务器列表
管道不仅可以连接两个命令,还可以将文件内容传递给 Python 程序。
先创建服务器列表文件 servers.txt,内容如下:
1 | web01 |
然后执行:
1 | cat servers.txt | python3 batch_input.py |
输出结果与前面的示例相同:
1 | 准备检查服务器:web01 |
在 Linux 中,还可以直接使用输入重定向:
1 | python3 batch_input.py < servers.txt |
这里的 < 表示将 servers.txt 文件的内容作为 Python 程序的标准输入。
对于这个简单场景,下面两种写法都能实现相同的效果:
1 | cat servers.txt | python3 batch_input.py |
1 | python3 batch_input.py < servers.txt |
第二种写法更加直接,因为不需要额外启动 cat 命令。
什么时候适合使用批量输入
假设我们已经有一个包含 200 台服务器名称的文件,希望逐行读取服务器列表,再交给 Python 程序处理。
如果使用 input(),通常需要编写循环,并由用户逐次提供输入;如果使用 sys.stdin,就可以直接读取文件或其他命令生成的数据。
例如:
1 | python3 batch_input.py < servers.txt |
这也为后续自动化运维打下了基础:Linux 命令负责生成数据,Python 程序负责读取和处理数据。
最后需要注意,管道默认传递的是前一个命令的标准输出。如果前一个命令将内容写入标准错误,这些错误信息不会自动成为后一个程序的标准输入。
因此,理解 sys.stdin 的关键是:它接收的不仅是键盘输入,还可以是通过管道或输入重定向传入的数据。
使用 sys.stderr 输出错误信息
在前面的例子中,正常输出通过标准输出显示。
但是,如果程序出现错误,我们是否可以把错误信息单独发送到标准错误通道?
当然可以。
创建文件 stderr_demo.py:
1 | import sys |
运行程序:
1 | python3 stderr_demo.py |
在默认终端环境下,你可能看到类似这样的内容:
1 | 正在检查服务器状态 |
这里需要注意:标准输出和标准错误都连接到终端时,两条通道的内容可能显示在同一个屏幕上。由于缓冲机制等原因,实际显示顺序不一定总与代码中的调用顺序一致。
sys.stderr.write() 与 sys.stdout.write() 的用法非常相似,区别在于它们面向不同的输出通道。
sys.stdout:用于正常结果。sys.stderr:用于错误信息、警告和诊断信息。
在实际运维脚本中,这种区分很有价值。例如,我们可以让正常巡检结果进入一个文件,同时将错误信息保存到另一个文件,方便后续分析。
在 Linux 中分别重定向输出
既然标准输出和标准错误是不同的通道,那么我们就可以利用 Linux 的重定向功能分别处理它们。
先创建文件 io_redirect_demo.py:
1 | import sys |
运行以下命令:
1 | python3 io_redirect_demo.py > normal.log 2> error.log |
这条命令中:
>:将标准输出重定向到normal.log。2>:将标准错误重定向到error.log。
这里的数字 2 表示标准错误对应的文件描述符。Linux 中,标准输入、标准输出和标准错误通常分别使用文件描述符 0、1 和 2。
执行结束后,查看正常输出文件:
1 | cat normal.log |
预期内容:
1 | 服务器检查开始 |
再查看错误输出文件:
1 | cat error.log |
预期内容:
1 | 警告:内存检查接口暂时不可用 |
这样,正常结果和错误信息就被分别保存了下来。
在 Linux 运维中,这种做法很常见。例如,执行批量巡检脚本时,我们可以将检查结果写入一个文件,将错误信息单独保存,便于后续定位问题。
还可以进一步理解为:
flowchart TD
A["Python 程序"] --> B["标准输出 stdout"]
A --> C["标准错误 stderr"]
B --> D["重定向符号 >"]
C --> E["重定向符号 2>"]
D --> F["normal.log"]
E --> G["error.log"]需要注意,sys.stderr.write() 只是将信息写入标准错误,并不会自动判断某个操作是否失败,也不会自动让程序以非零状态退出。错误处理逻辑和程序退出状态是另外需要学习的内容。
什么时候使用这些功能
学习到这里,我们已经认识了 sys.stdin、sys.stdout 和 sys.stderr。
可以先记住下面这张表:
| 功能 | 用途 | 常见使用方式 |
|---|---|---|
sys.stdin | 读取标准输入 | 逐行读取终端或管道传入的数据 |
sys.stdout | 输出正常结果 | 写入字符串、控制输出格式 |
sys.stderr | 输出错误或诊断信息 | 输出警告、错误提示 |
对于刚开始学习 Python 的读者,不需要刻意放弃 input() 和 print()。
在简单的交互程序中,继续使用它们就很好理解:
1 | name = input("请输入姓名:") |
当我们开始编写 Linux 运维脚本、处理大量输入数据、使用管道,或者需要将正常输出与错误信息分开保存时,再逐步使用 sys 模块提供的这些功能。
可以用下面的思路帮助自己选择合适的方式:
flowchart TD
A["程序需要接收或输出数据"] --> B{"需要什么操作?"}
B -->|与用户交互| C["使用 input()"]
B -->|批量读取数据| D["使用 sys.stdin"]
B -->|显示正常结果| E["使用 print() 或 sys.stdout"]
B -->|输出错误与诊断信息| F["使用 sys.stderr"]
D --> G["可配合管道或输入重定向"]
E --> H["可配合 > 重定向"]
F --> I["可配合 2> 重定向"]这才是学习新工具的意义:不是为了使用更复杂的写法,而是在遇到合适的问题时,知道自己还有其他解决方法。
写在最后
通过本篇,我们认识了 Python 中的标准输入、标准输出和标准错误,学习了如何使用 sys.stdin 读取输入,使用 sys.stdout 输出正常内容,以及使用 sys.stderr 输出错误信息。
我们还区分了交互式输入与批量输入,理解了 input() 更适合简单的交互场景,而 sys.stdin 可以配合循环、Linux 管道和输入重定向,逐行处理文件或其他程序提供的数据。
最后,我们将这些知识与 Linux 的输出重定向结合起来,体验了如何把正常结果和错误信息分别保存到不同的文件中。
下一篇,我们将继续学习 Python 的数据流与字节序列,进一步理解程序如何读取和写入数据,以及字符串和字节数据之间有什么区别。
