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() 与金额计算避坑

为什么 '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,都会频繁用到这些操作。