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

为什么 'Hello' + 'World' 可以拼接,而 'Hello' * 3 却不是把字符乘以 3?为什么字符串创建后,不能直接修改其中的一个字符?为什么日志里用 find() 查找关键字,找不到时会返回 -1,而不是直接报错?

这些问题看起来基础,却是处理日志、配置文件、URL 时最容易踩坑的地方。这篇文章我们就从字符串的本质开始,逐步搞懂字符串的创建、常用方法和基本操作,再通过几个运维场景把知识真正用起来。

上一篇我们学习了 Python 数值类型和运算符,掌握了整数、浮点数、整除和幂运算。现在,程序已经能计算了。接下来要解决的问题是:如何正确地读取、处理和输出文本?

Python 里的字符串是什么?

Python 中的字符串(String)是由零个或多个字符组成的不可变序列,使用 str 类型表示。

这里有两个关键词:序列和不可变。

序列意味着字符串中的字符有先后顺序,可以通过索引访问,也可以使用切片截取其中一部分。

不可变意味着字符串对象创建后,不能直接修改其中的某个字符。如果需要改变文本内容,通常要通过拼接、替换等操作创建新的字符串。

例如:

1
2
3
4
5
s = "hello"
print(s)

# 尝试修改第一个字符
s[0] = "H"

运行结果:

1
2
3
4
5
6
hello
Traceback (most recent call last):
File "<python-input-1>", line 5, in <module>
s[0] = "H"
~^^^
TypeError: 'str' object does not support item assignment

为什么会报错?因为 Python 不允许直接修改字符串中的单个字符。

如果想把 hello 变成 Hello,可以这样做:

1
2
3
s = "hello"
s = "H" + s[1:]
print(s)

运行结果:

1
Hello

这里并不是修改了原字符串,而是创建了一个新字符串,然后让变量 s 指向新字符串。

字符串不可变有助于减少共享文本被意外修改的问题,但这并不意味着整个程序自动具备线程安全性。

怎么创建字符串?

单引号和双引号

Python 可以使用单引号或双引号创建字符串,两者没有本质区别。

1
2
3
4
a = "This is a literal string"
b = 'and so is this'

print(a, b)

运行结果:

1
This is a literal string and so is this

如果字符串中包含引号,可以选择另一种引号包裹字符串,减少转义符的使用。

1
2
print('"Double quotes" inside of single quotes')
print("'Single quotes' inside of double quotes")

运行结果:

1
2
"Double quotes" inside of single quotes
'Single quotes' inside of double quotes

多行字符串

如果字符串内容比较长,或者需要保留换行,可以使用三引号。

1
2
3
4
message = """Royal Straight Flush
多行文本"""

print(message)

运行结果:

1
2
Royal Straight Flush
多行文本

Python 还支持使用反斜杠 \ 连接跨行的普通字符串:

1
2
3
4
message = "This is a long string just to " \
"illustrate continuation"

print(message)

运行结果:

1
This is a long string just to illustrate continuation

注意,反斜杠续行会连接代码行,不会自动在字符串中插入换行符。如果需要保留换行,使用三引号或显式写入 \n 更合适。

原始字符串

在 Windows 路径、正则表达式等场景中,经常会遇到反斜杠。

例如:

1
2
path = "C:\\Users\\xiaohui\\Documents"
print(path)

输出:

1
C:\Users\xiaohui\Documents

因为普通字符串中的反斜杠可能参与转义,所以需要写成 \\。

如果使用原始字符串,可以让反斜杠不按普通字符串的方式解释:

1
2
path = r"C:\Users\xiaohui\Documents"
print(path)

输出:

1
C:\Users\xiaohui\Documents

原始字符串使用 r 或 R 前缀,常用于 Windows 路径和正则表达式。

不过,原始字符串并不意味着反斜杠完全不受语法限制。例如,下面的写法会导致语法错误:

1
2
# 错误示例:原始字符串不能以单个反斜杠结尾
# path = r"C:\temp\"

常见转义序列

普通字符串中的反斜杠可以引出转义序列。

转义序列含义
\n换行
\t水平制表符
\\反斜杠
\'单引号
\"双引号
\xhh两位十六进制数表示的字符
\uXXXX四位十六进制数表示的 Unicode 码位
\UXXXXXXXX八位十六进制数表示的 Unicode 码位

例如:

1
2
3
print("第一行\n第二行")
print("Name:\txiaohui")
print("A as hexadecimal: \x41")

运行结果:

1
2
3
4
第一行
第二行
Name: xiaohui
A as hexadecimal: A

其中,\x41 表示十六进制数 41 对应的字符,也就是字母 A。

常用字符串方法

Python 提供了很多字符串方法,可以用来判断文本内容、查找关键字、替换文本、分割字符串以及清理空白字符。

需要记住一点:字符串不可变,因此大多数字符串处理方法会返回处理后的新字符串,而不是直接修改原字符串。

判断类方法

先来看一个常见场景:检查 URL 的前缀和后缀。

1
2
3
4
5
6
7
8
9
url = "www.linuxcenter.cn"

print(url.startswith("www"))
print(url.endswith(".org"))

new_url = url.upper()

print("ORIGINAL:", url)
print("RETURNED:", new_url)

运行结果:

1
2
3
4
True
False
ORIGINAL: www.linuxcenter.cn
RETURNED: WWW.LINUXCENTER.CN

这里用到了三个方法:

  • startswith():判断字符串是否以指定内容开头。
  • endswith():判断字符串是否以指定内容结尾。
  • upper():返回转换为大写后的新字符串。

可以看到,调用 upper() 后,原来的 url 仍然是小写。这正是字符串不可变的体现。

startswith() 和 endswith() 还可以接收元组,一次检查多个前缀或后缀:

1
2
3
filename = "server.log"

print(filename.endswith((".log", ".txt", ".conf")))

运行结果:

1
True

在检查文件类型、URL 前缀或日志格式时,这种写法很方便。

判断字符的类型

Python 还提供了一组用于判断字符串内容的方法。

1
2
3
4
print("AbCDe".isalpha(), "AbCd123".isalpha())
print("123".isnumeric(), "12.3".isnumeric())
print(" \t\n".isspace(), "a b\t\n".isspace())
print("ABCD".isupper(), "abcd".isupper())

运行结果:

1
2
3
4
True False
True False
True False
True False

这些方法分别表示:

  • isalpha():字符串中的字符是否全部为字母。
  • isnumeric():字符串中的字符是否全部属于 Unicode 数字字符。
  • isspace():字符串中的字符是否全部为空白字符。
  • isupper():字符串中是否存在区分大小写的字符,且这些字符全部为大写。

注意,这些方法并不等同于完整的业务校验。例如,isnumeric() 返回 True,不代表这个字符串一定能直接转换成十进制整数;isalpha() 也不代表字符串只能包含英文字母。

查找关键字

处理日志时,经常需要查找某个关键字是否存在。

1
2
3
4
5
6
7
8
word = "is"
sentence = "The capital of Mississippi is Jackson."

position = sentence.find(word)

print("First:", position)
print("Second:", sentence.find(word, position + 1))
print("Count:", sentence.count(word))

运行结果:

1
2
3
First: 16
Second: 19
Count: 3

这里用到了两个方法:

  • find():返回子字符串第一次出现的位置,也可以指定开始查找的位置。
  • count():统计子字符串出现的次数。

需要特别注意,find() 找不到内容时不会抛出异常,而是返回 -1。

1
2
3
4
log = "2026-10-14 INFO service started"

print(log.find("INFO"))
print(log.find("ERROR"))

运行结果:

1
2
11
-1

-1 表示没有找到。如果直接把这个结果用于索引或切片计算,可能导致错误的业务逻辑。

例如,下面的代码就存在问题:

1
2
3
4
log = "service started"
position = log.find("ERROR")

print(log[position:])

因为 position 是 -1,所以程序会取出字符串最后一个字符,而不是报错。

更稳妥的写法是先检查返回值:

1
2
3
4
5
6
7
log = "service started"
position = log.find("ERROR")

if position != -1:
print(log[position:])
else:
print("没有找到 ERROR")

如果希望查找失败时抛出 ValueError,也可以使用 index()。

对齐与替换

有时我们需要把文本对齐,或者将某些内容替换成新的内容。

1
2
3
4
5
6
7
8
9
word = "is"

print("Right Justified:", word.rjust(15), "|")
print("Left Justified:", word.ljust(15, "*"), "|")

data = "1 4 1 1abc"

print("replace all:", data.replace("1", "0"))
print("replace two:", data.replace("1", "0", 2))

运行结果:

1
2
3
4
Right Justified:              is |
Left Justified: is************* |
replace all: 0 4 0 0abc
replace two: 0 4 0 1abc

其中:

  • rjust():在左侧填充字符,让字符串达到指定宽度。
  • ljust():在右侧填充字符,让字符串达到指定宽度。
  • replace():将指定的子字符串替换为新内容。

replace() 的第三个参数表示最多替换多少次。如果省略,就会替换所有匹配内容。

注意,replace() 不会直接修改原字符串。如果需要使用替换结果,应当接收返回值:

1
2
3
4
text = "hello world"
text.replace("hello", "Hi")

print(text)

输出仍然是:

1
hello world

正确的写法是:

1
2
3
4
text = "hello world"
text = text.replace("hello", "Hi")

print(text)

输出:

1
Hi world

分割字符串

在运维脚本中,我们经常需要把一行文本拆分成多个字段。

1
2
3
4
5
6
data = "1 4 1 1abc"

pieces = data.split(" ")

print(pieces)
print(type(pieces))

运行结果:

1
2
['1', '4', '1', '1abc']
<class 'list'>

split() 会返回一个列表(list),而不是字符串。

这里还有一个很容易踩坑的地方:split(" ") 和 split() 并不完全一样。

1
2
3
4
data = "nginx   active\t running"

print(data.split(" "))
print(data.split())

运行结果:

1
2
['nginx', '', '', 'active\t', 'running']
['nginx', 'active', 'running']

区别在于:

  • split(" "):只按普通空格分割,连续空格可能产生空字符串,制表符不会作为分隔符。
  • split():不指定分隔符时,按连续的空白字符分割,并忽略字符串两端的空白。

因此,处理空格数量不固定的文本时,通常优先考虑 split()。

不过,日志格式如果有固定字段规则,不能只依赖简单分割。例如,最后一个字段可能包含空格,这时可以指定最多分割次数:

1
2
3
4
5
log = "2026-10-14 12:00:01 ERROR disk full"

fields = log.split(" ", 3)

print(fields)

运行结果:

1
['2026-10-14', '12:00:01', 'ERROR', 'disk full']

这样就能保留最后的错误信息,不会把 disk full 拆成两个字段。

去除空白字符

读取文件、接收用户输入或处理日志时,字符串两端经常会有多余的空格、换行符或制表符。

Python 提供了三个常用方法:

  • strip():去除两端的空白字符。
  • lstrip():去除左侧的空白字符。
  • rstrip():去除右侧的空白字符。

看一个例子:

1
2
3
4
5
data = "\t  \nabc  def\t  \n"

print(len(data), ":", len(data.strip()))
print(len(data), ":", len(data.rstrip()))
print(len(data), ":", len(data.lstrip()))

运行结果:

1
2
3
16 : 8
16 : 12
16 : 12

为什么 strip() 之后长度变成了 8?

因为 abc def 中间的两个空格会保留,只有字符串两端的空白字符被去除了。

还需要注意,strip() 默认会识别许多 Unicode 空白字符,通常也包括中文全角空格,并不只是普通的 ASCII 空格。

如果要清理指定字符,可以给方法传入参数:

1
2
3
text = "###hello###"

print(text.strip("#"))

运行结果:

1
hello

这里的 strip("#") 会去除两端的 #,但不会修改中间的字符。

另外,strip("#abc") 的含义也不是删除完整的字符串 #abc,而是从两端不断去除属于 #abc 这个字符集合的字符。这一点在清理文本时要特别注意。

序列操作:拼接、重复与成员测试

字符串是一种序列,因此可以使用一些通用的序列操作。

字符串拼接

最常见的方式是使用 +:

1
2
3
4
5
6
first_name = "Casey"
last_name = "Jackson"

full_name = first_name + " " + last_name

print(full_name)

运行结果:

1
Casey Jackson

不过,Python 还有一种特殊语法:相邻的字符串字面量可以自动拼接,即使中间没有 +。

1
2
3
full_name = "Casey" " " "Jackson"

print(full_name)

运行结果:

1
Casey Jackson

注意,这种自动拼接只适用于相邻的字符串字面量,不适用于运行时变量。

例如:

1
2
3
4
5
a = "Casey"
b = "Jackson"

# print(a b) # 语法错误
print(a + " " + b)

字符串重复

使用 * 可以重复字符串。

1
2
3
4
stars = "*" * 12
pounds = 5 * "#"

print(stars, ":", pounds)

运行结果:

1
************ : #####

这里的 * 并不是把每个字符的数值乘以指定数字,而是将整个字符串重复指定次数。

所以:

1
2
print("Hello" + "World")
print("Hello" * 3)

运行结果:

1
2
HelloWorld
HelloHelloHello

这也是为什么字符串可以使用 *,却不具有数值乘法的含义。

成员测试

使用 in 可以判断一个字符或子字符串是否存在于字符串中。

1
2
3
x = "Hello there"

print("t" in x, "ell" in x, "hell" in x)

运行结果:

1
True True False

这里的匹配区分大小写,所以 "hell" 与 "Hell" 并不相同。

在运维场景中,可以用 in 判断日志是否包含某个关键字:

1
2
3
4
log = "2026-10-14 ERROR disk full"

if "ERROR" in log:
print("发现错误日志")

运行结果:

1
发现错误日志

对于只需要判断内容是否存在的场景,in 通常比先调用 find() 再判断返回值更加直观。

索引与切片

既然字符串是序列,就可以通过索引访问字符,通过切片截取部分内容。

1
2
3
4
5
spam = "Spam and eggs"

print(spam[0], spam[3], spam[-1], sep=" | ")
print(spam[2:7], spam[5:], spam[:8], sep=" | ")
print(spam[-3:-1], spam[-3:], spam[:-1], sep=" | ")

运行结果:

1
2
3
S | m | s
am an | and eggs | Spam an
gg | ggs | Spam and egg

这里需要记住:

  • 索引从 0 开始。
  • 负数索引从字符串末尾开始,-1 表示最后一个字符。
  • 切片采用左闭右开区间,包含起始位置,不包含结束位置。
  • 省略起始位置表示从开头开始,省略结束位置表示一直取到末尾。

切片的基本形式是:

1
s[start:end:step]

其中,step 表示步长。

例如:

1
2
3
alphabet = "abcdefghijklmnopqrstuvwxyz"

print(alphabet[2:18:3])

运行结果:

1
cfilor

从索引 2 开始,每隔三个位置取一个字符,直到索引 18 之前结束。

切片还有一个很实用的特点:超出字符串范围的结束索引通常不会报错,而是截取到字符串实际结束的位置。

1
2
3
text = "hello"

print(text[1:100])

运行结果:

1
ello

但要注意,直接访问不存在的索引,例如 text[100],仍然会触发 IndexError。

字符串格式化

当我们需要把变量放进提示信息、日志或报告中时,就会用到字符串格式化。

Python 支持多种格式化方式。现代 Python 代码中,推荐优先使用 f-string;format() 和 % 格式化在已有项目中也很常见。

使用 f-string

1
2
3
4
name = "nginx"
status = "active"

print(f"服务 {name} 当前状态:{status}")

运行结果:

1
服务 nginx 当前状态:active

f-string 使用字符串前缀 f,可以直接在字符串中通过 {} 插入变量或表达式。

例如,在运维脚本中输出磁盘使用率:

1
2
3
4
disk = "/dev/sda1"
usage = 82.567

print(f"磁盘 {disk} 当前使用率:{usage:.2f}%")

运行结果:

1
磁盘 /dev/sda1 当前使用率:82.57%

其中,.2f 表示按照浮点数格式保留两位小数。

使用 format()

format() 同样可以将变量插入字符串。

1
2
3
4
5
6
7
8
9
name = "John"
last_name = "Smith"
middle_initial = "C."

template = "First: {}\tLast Name: {}\tMiddle Initial: {}"

formatted = template.format(name, last_name, middle_initial)

print(formatted)

运行结果:

1
First: John	Last Name: Smith	Middle Initial: C.

如果使用关键字参数,代码会更加清晰:

1
2
3
4
5
6
7
8
9
dimensions = "Type: {type}\nHeight: {height}, Width: {width}"

result = dimensions.format(
height=50,
width=25,
type="Box"
)

print(result)

运行结果:

1
2
Type: Box
Height: 50, Width: 25

字符串格式化方式对比

1
2
3
4
5
6
name = "nginx"
status = "active"

print(f"服务 {name} 当前状态:{status}")
print("服务 {} 当前状态:{}".format(name, status))
print("服务 %s 当前状态:%s" % (name, status))

三种方式都可以完成格式化,但在编写新的 Python 代码时,f-string 通常更简洁直观。

字符串处理流程

处理文本时,我们通常会经历创建、清理、查找、分割和格式化等步骤。

flowchart LR
    A[原始文本] --> B[创建与转义]
    B --> C[清理空白字符]
    C --> D[查找与分割]
    D --> E[拼接或替换]
    E --> F[格式化输出]

例如,读取一行日志后,可以先去掉首尾空白,再判断是否包含错误关键字,最后将需要的信息提取出来。

这类操作是 Python 自动化运维脚本中的常见基础。

生产环境常见坑

字符串的基本语法并不复杂,但在真实的运维场景中,仍然有几个容易出错的地方。

  • 字符串不可变。 replace()、upper() 等方法通常返回新字符串。如果需要使用处理后的结果,记得接收返回值。
  • find() 找不到会返回 -1。 不要直接将返回值当作有效索引使用,应先判断是否找到。
  • split(" ") 与 split() 不同。 日志中的空格数量不固定时,直接使用 split(" ") 可能产生空字符串;需要根据输入格式选择合适的分割方式。
  • strip() 只处理两端。 它不会自动删除字符串中间的空白,也不会自动清理任意标点符号。
  • 大量字符串拼接需要注意性能。 循环中反复使用 + 可能产生较多中间字符串对象;如果需要拼接大量文本,可以先收集到列表,再使用 join()。
  • 字符串匹配通常区分大小写。 比如 "ERROR" in log 不会匹配小写的 "error"。如果业务要求忽略大小写,可以先使用 lower() 或 casefold() 统一处理。
  • 简单字符串操作不等于完整的日志解析。 如果日志包含引号、转义字符或复杂字段,应根据实际格式选择更合适的解析方法。

使用 join() 拼接大量字符串

例如,需要把多个服务名称拼接成一行:

1
2
3
4
5
services = ["nginx", "sshd", "crond"]

result = ", ".join(services)

print(result)

运行结果:

1
nginx, sshd, crond

join() 会使用指定的分隔符连接可迭代对象中的字符串。这里的分隔符是 ", "。

需要注意,参与拼接的元素必须是字符串。如果列表中包含整数等其他类型,需要先转换为字符串。

综合练习:处理一条运维日志

假设我们有这样一条日志:

1
2025-10-28 12:00:01 ERROR disk full from 192.168.1.10

现在需要完成几个任务:

  • 去掉日志首尾的空白字符。
  • 提取日期、时间、日志级别和消息内容。
  • 判断日志是否为 ERROR。
  • 将消息中的 IP 地址替换为脱敏标记。
  • 使用 f-string 输出整理后的结果。

可以这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
log = "  2025-10-28 12:00:01 ERROR disk full from 192.168.1.10  "

# 去掉首尾空白
log = log.strip()

# 将日志拆分为日期、时间、级别和消息
fields = log.split(" ", 3)

date = fields[0]
time = fields[1]
level = fields[2]
message = fields[3]

# 判断日志级别
if level == "ERROR":
print("发现错误日志")

# 脱敏指定 IP
message = message.replace("192.168.1.10", "[已脱敏IP]")

# 格式化输出
print(f"日期:{date}")
print(f"时间:{time}")
print(f"级别:{level}")
print(f"消息:{message}")

运行结果:

1
2
3
4
5
发现错误日志
日期:2025-10-28
时间:12:00:01
级别:ERROR
消息:disk full from [已脱敏IP]

这个示例展示了字符串处理的基本流程。不过,示例中的 IP 脱敏使用的是精确文本替换,只适用于已知的固定 IP。如果需要批量脱敏不同 IP,或者处理复杂的日志格式,可以进一步学习 Python 正则表达式和日志解析方法。

写在最后

这一篇我们从字符串的本质出发,学习了 Python 文本处理的基础知识:

  • 字符串的不可变特性,以及单引号、双引号、三引号、原始字符串和转义序列。
  • startswith()、endswith()、find()、replace()、split()、strip() 等常用方法。
  • 字符串拼接、重复、成员测试、索引和切片。
  • 使用 f-string 和 format() 格式化输出。
  • 将字符串操作应用到日志字段提取、关键字判断和 IP 脱敏等运维场景。

对于 Linux 运维人员来说,字符串处理是编写自动化脚本的重要基础。无论是分析 Nginx 日志、读取配置文件,还是调用云平台 API,都会频繁用到这些操作。