1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Python 系列(一) 从零开始认识 Python,入门其实没那么难
  2. Python 系列(二) 学 Python 的第一步,把开发环境搭建好
  3. Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
  4. Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
  5. Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
  6. Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
  7. Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
  8. Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
  9. Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
  10. Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
  11. Python 系列(十一) list、tuple、set、dict,到底该怎么选?
  12. Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
  13. Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
  14. Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
  15. Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
  16. Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
  17. Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
  18. Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
  19. Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
  20. Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
  21. Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
  22. Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
  23. Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
  24. Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
  25. Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
  26. Python 系列(二十六) Python 模块入门,从 import 到模块复用
  27. Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
  28. Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
  29. Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
  30. Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
  31. Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
  32. Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
  33. Python 系列(三十三) 什么是类和对象?零基础理解 Python class
  34. Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
  35. Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
  36. Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
  37. Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
  38. Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
  39. Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
  40. Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
  41. Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
  42. Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
  43. Python 系列(四十三) 别只会处理异常!学会自定义异常,让程序更容易维护
  44. Python 系列(四十四) 从零认识 assert 断言,让程序学会检查自己
  45. Python 系列(四十五) 使用 sys 模块处理标准输入与输出
  46. Python 系列(四十六) 使用 open() 读取和写入文件
  47. Python 系列(四十七) 学会读写文本文件,让程序保存运行结果
  48. Python 系列(四十八) 搞懂 bytes、bytearray 和二进制读写
  49. Python 系列(四十九) 使用 seek() 和 tell() 灵活控制文件读取位置
  50. Python 系列(五十) 掌握 os 和 os.path,实现文件与目录自动化管理
  51. Python 系列(五十一) 掌握列表推导式,轻松实现列表生成与数据筛选
  52. Python 系列(五十二) 掌握字典推导式,让字典创建更简单
  53. Python 系列(五十三) 从零理解生成器,轻松处理大量数据
  54. Python 系列(五十五) 使用 zip() 同时遍历多个集合

前面我们学习了如何使用 input() 接收用户输入,以及使用 print() 在屏幕上显示内容。

例如,编写一个简单的程序:

1
2
name = input("请输入你的姓名:")
print(f"你好,{name}!")

运行程序后,Python 会显示提示信息,等待我们输入姓名,然后输出问候语。

对于大多数简单的程序来说,input() 和 print() 已经足够了。

但是,如果我们希望更直接地控制程序的输入和输出呢?

例如:

  • 从标准输入读取数据,而不是只依赖 input()。
  • 将内容写入标准输出,并自行控制换行。
  • 将错误信息单独输出,方便后续排查问题或收集日志。
  • 将 Linux 命令生成的数据传递给 Python 程序,进行批量处理。

这时,我们就可以使用 Python 提供的 sys 模块。

认识 sys 模块

sys 是 Python 标准库中的一个模块,它提供了一些与 Python 解释器及其运行环境交互的功能。

可以把模块简单理解为一个已经准备好的工具箱。需要使用其中的工具时,我们先通过 import 导入模块,再调用对应的功能。

例如:

1
2
3
import sys

print(sys.version)

这里,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
2
3
4
import sys

sys.stdout.write("Hello, Python!\n")
sys.stdout.write("正在检查服务器状态\n")

运行程序:

1
python3 stdout_demo.py

预期输出:

1
2
Hello, Python!
正在检查服务器状态

这段代码中:

  • import sys:导入 sys 模块。
  • sys.stdout:获取标准输出对象。
  • write():向标准输出写入字符串。
  • \n:表示换行。

这里有一个需要特别注意的区别:sys.stdout.write() 不会自动在字符串末尾添加换行符。

例如:

1
2
3
4
import sys

sys.stdout.write("Hello")
sys.stdout.write("Python")

输出结果为:

1
HelloPython

因为两次写入的内容之间没有换行符,所以它们会直接连接在一起。

如果改成:

1
2
3
4
import sys

sys.stdout.write("Hello\n")
sys.stdout.write("Python\n")

输出结果就会变成:

1
2
Hello
Python

那么,sys.stdout.write() 和之前学过的 print() 有什么区别呢?

1
print("Hello")

默认情况下,print() 会在输出内容后添加换行符。

而:

1
2
3
import sys

sys.stdout.write("Hello")

只会写入指定的字符串,不会自动添加换行符。

此外,sys.stdout.write() 在正常写入字符串的情况下,会返回实际写入的字符数。不过,刚开始学习时,我们只需要先掌握它的基本输出功能即可。

对于普通的屏幕输出,优先使用 print() 通常更方便;当我们需要更直接地控制输出内容时,可以考虑使用 sys.stdout.write()。

使用 sys.stdin 读取输入

除了输出内容,我们还可以通过 sys.stdin 读取标准输入。

创建文件 stdin_demo.py:

1
2
3
4
5
6
7
import sys

print("请输入服务器名称:", end="")

server_name = sys.stdin.readline()

print(f"准备检查服务器:{server_name.strip()}")

运行程序:

1
python3 stdin_demo.py

在提示信息后输入 web01,再按回车,输出示例为:

1
2
请输入服务器名称:web01
准备检查服务器: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
2
3
server_name = input("请输入服务器名称:")

print(f"准备检查服务器:{server_name}")

运行程序:

1
python3 interactive_input.py

此时,程序会显示:

1
请输入服务器名称:

程序会等待用户输入服务器名称。只有输入 web01 并按下回车后,程序才会继续执行。

1
2
请输入服务器名称:web01
准备检查服务器:web01

整个过程可以理解为:

flowchart TD
    A["启动 Python 程序"] --> B["显示输入提示"]
    B --> C["等待用户输入"]
    C --> D["用户输入 web01 并按回车"]
    D --> E["程序获得服务器名称"]
    E --> F["执行后续处理"]

在这个过程中,用户和程序之间存在明显的交互:程序提出要求,用户提供数据,程序再根据数据执行后续操作。

这种方式适合以下场景:

  • 让管理员输入需要检查的服务器名称。
  • 让用户输入查询条件。
  • 让用户选择操作菜单。
  • 在执行某项操作前,让用户输入必要的参数。

对于这类场景,input() 通常是最简单、最直观的选择。

什么是批量输入

假设我们需要检查的不是一台服务器,而是 200 台服务器。

如果继续使用交互式输入,就可能需要反复输入服务器名称。这不仅效率低,也不适合自动化任务。

更合理的方式是:提前准备好服务器列表,让程序从文件或其他数据源接收这些内容,再自动逐条处理。

例如,创建服务器列表文件 servers.txt:

1
2
3
web01
web02
web03

然后编写程序 batch_input.py:

1
2
3
4
5
6
7
8
9
import sys

for line in sys.stdin:
server_name = line.strip()

if not server_name:
continue

print(f"准备检查服务器:{server_name}")

运行程序:

1
python3 batch_input.py < servers.txt

预期输出:

1
2
3
准备检查服务器:web01
准备检查服务器:web02
准备检查服务器:web03

这里没有要求管理员逐个输入服务器名称。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
2
3
4
5
6
7
for line in sys.stdin:
server_name = line.strip()

if not server_name:
continue

print(f"准备检查服务器:{server_name}")

其中:

1
for line in sys.stdin:

表示从标准输入中逐行读取数据。

当输入中有多行内容时,Python 会逐行读取,每读取一行,就执行一次循环中的代码。

接下来:

1
server_name = line.strip()

去除当前行首尾的空白字符,方便后续处理。

然后:

1
2
if not server_name:
continue

判断清理后的服务器名称是否为空。如果为空,就使用 continue 跳过本次循环,继续处理下一行。

最后:

1
print(f"准备检查服务器:{server_name}")

输出当前读取到的服务器名称。

当标准输入中的内容全部读取完毕后,循环就会结束。

通过管道传入多台服务器

执行以下命令:

1
printf 'web01\nweb02\nweb03\n' | python3 batch_input.py

预期输出:

1
2
3
准备检查服务器:web01
准备检查服务器:web02
准备检查服务器:web03

其中,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
2
3
web01
web02
web03

然后执行:

1
cat servers.txt | python3 batch_input.py

输出结果与前面的示例相同:

1
2
3
准备检查服务器:web01
准备检查服务器:web02
准备检查服务器:web03

在 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
2
3
4
5
import sys

print("正在检查服务器状态")

sys.stderr.write("警告:无法获取服务器的磁盘使用率\n")

运行程序:

1
python3 stderr_demo.py

在默认终端环境下,你可能看到类似这样的内容:

1
2
正在检查服务器状态
警告:无法获取服务器的磁盘使用率

这里需要注意:标准输出和标准错误都连接到终端时,两条通道的内容可能显示在同一个屏幕上。由于缓冲机制等原因,实际显示顺序不一定总与代码中的调用顺序一致。

sys.stderr.write() 与 sys.stdout.write() 的用法非常相似,区别在于它们面向不同的输出通道。

  • sys.stdout:用于正常结果。
  • sys.stderr:用于错误信息、警告和诊断信息。

在实际运维脚本中,这种区分很有价值。例如,我们可以让正常巡检结果进入一个文件,同时将错误信息保存到另一个文件,方便后续分析。

在 Linux 中分别重定向输出

既然标准输出和标准错误是不同的通道,那么我们就可以利用 Linux 的重定向功能分别处理它们。

先创建文件 io_redirect_demo.py:

1
2
3
4
5
6
import sys

print("服务器检查开始")

sys.stdout.write("磁盘使用率:65%\n")
sys.stderr.write("警告:内存检查接口暂时不可用\n")

运行以下命令:

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
2
服务器检查开始
磁盘使用率:65%

再查看错误输出文件:

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
2
name = input("请输入姓名:")
print(f"你好,{name}!")

当我们开始编写 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 的数据流与字节序列,进一步理解程序如何读取和写入数据,以及字符串和字节数据之间有什么区别。