下面我将从最简单到最健壮的方式,详细讲解如何在 sed 中使用变量。

核心问题:Shell 和 Sed 的两层解析
当你运行 sed "s/$var/new/g" 文件时,会发生两件事:
- Shell 层面:Shell 首先会读取这个命令行,它会看到
$var,并将其替换为变量的实际值。var="hello/world",那么命令行在传给sed之前,会先变成sed "s/hello/world/new/g" 文件。 - Sed 层面:
sed接收到的命令是s/hello/world/new/g。sed会尝试用 作为分隔符,将hello替换为world,然后再把/new/g当作要替换的文本,这显然不是你想要的结果。
更严重的是,如果变量内容包含特殊字符,比如路径分隔符 、&(代表替换内容)、、 等,就会导致 sed 命令语法错误或产生非预期的替换。
简单引号(适用于变量内容简单)
这是最基本的方法,但只在变量内容不包含 sed 特殊字符时才安全。
双引号包裹 sed 命令
用双引号 包裹 sed 命令,允许 Shell 进行变量替换。

#!/bin/bash file="test.txt" old_text="apple" new_text="orange" # 双引号,允许 $old_text 和 $new_text 被替换 sed -i "s/$old_text/$new_text/g" "$file"
风险:$old_text 的值是 a/b/c,命令会变成 s/a/b/c/orange/g,导致语法错误。
单引号包裹 sed 命令
用单引号 包裹 sed 命令,会阻止 Shell 进行任何替换(包括变量),所以你必须先把变量拼接到字符串里。
#!/bin/bash file="test.txt" old_text="apple" new_text="orange" # 先用双引号拼接,然后用单引号包裹整个 sed 命令 # 这种写法不常用,且同样有风险 sed -i 's/'"$old_text"'/'"$new_text"'/g' "$file"
这种写法虽然也能工作,但和双引号方式一样,对特殊字符没有免疫力。
最佳实践 - 使用不同的分隔符(推荐)
这是最常用且最简单有效的解决方法。sed 的 s 命令允许使用任意单个字符作为分隔符,而不仅仅是 。

技巧:如果替换文本中包含 ,就选择另一个不冲突的字符作为分隔符,、、、 等。
#!/bin/bash file="test.txt" path="/usr/local/bin" # 变量中包含 / new_path="/opt/bin" # 使用 | 作为分隔符,完美解决 / 冲突问题 sed -i "s|$path|$new_path|g" "$file" # 另一个例子 url="http://example.com" new_url="https://newsite.org" sed -i "s|$url|$new_url|g" "$file"
优点:
- 简单直观。
- 能解决 90% 以上的问题。
- 无需安装额外工具。
缺点:
- 如果变量中同时包含了 和 ,那么这种方法也可能失效(虽然非常罕见)。
最健壮的方法 - 使用 printf 和 here-string
对于包含任意特殊字符(如 \n, &, , 等)的变量,这是最安全、最可靠的方法,它利用 printf 来正确格式化 sed 命令,然后通过 here-string (<<)将其传递给sed`。
#!/bin/bash file="test.txt" # 定义一个包含各种特殊字符的复杂字符串 special_text="This is a line with / and & and | and *" replacement="This is a new line" # 使用 printf 和 here-string # 1. printf -v cmd ... 将格式化后的命令存入变量 cmd # 2. -v 确保命令中的特殊字符(如 &, /)被正确转义 # 3. sed -f - 从标准输入读取脚本,here-string <<EOF 将 cmd 的内容作为输入 printf -v cmd 's|%s|%s|g' "$(printf "%s\n" "$special_text" | sed 's/[\/&]/\\&/g')" "$replacement" sed -i -f - "$file" <<< "$cmd"
分解说明:
-
printf -v cmd 's|%s|%s|g' ...:printf -v cmd:将格式化后的结果存入变量cmd,而不是打印到屏幕。's|%s|%s|g':这是sed命令的模板,我们选择 作为分隔符。- 命令替换,执行括号内的命令并获取其输出。
-
$(printf "%s\n" "$special_text" | sed 's/[\/&]/\\&/g'):- 这是对
$special_text变量进行安全转义的核心部分。 printf "%s\n" "$special_text":确保变量内容被正确处理,即使其中包含空格。sed 's/[\/&]/\\&/g':这是转义sed特殊字符的关键。[\/&]:匹配 和&这两个在sed替换模式中最需要转义的字符。\\&:将它们替换为\&,在sed中,\&代表字面意义上的&字符,而不是“匹配的文本”。
- 这是对
-
sed -i -f - "$file" <<< "$cmd":sed -f -:告诉sed从标准输入 () 读取命令(脚本)。<<< "$cmd":here-string,将变量cmd的内容作为标准输入传递给sed。
优点:
- 绝对安全:可以处理包含任何特殊字符的变量。
- 非常健壮,适用于脚本和自动化任务。
缺点:
- 语法比较复杂,不易于记忆和理解。
使用 awk 替代(对于简单替换)
如果你的需求仅仅是进行简单的文本替换,awk 也是一个非常好的选择,它在处理变量方面通常比 sed 更直观、更不容易出错。
#!/bin/bash
file="test.txt"
old_text="apple"
new_text="orange"
# awk 的 -v 选项用于定义变量,语法清晰
awk -v old="$old_text" -v new="$new_text" '{gsub(old, new); print}' "$file" > temp && mv temp "$file"
# 或者使用 -i inplace (GNU awk)
# gawk -i inplace -v old="$old_text" -v new="$new_text" '{gsub(old, new); print}' "$file"
优点:
- 变量传递非常清晰(
-v var=value)。 - 内置的
gsub()函数功能强大。 - 通常比复杂的
sed命令更易读。
缺点:
awk通常比sed更“重”,对于极其简单的替换可能显得有些“杀鸡用牛刀”。
总结与建议
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 双引号 | 简单,无特殊字符 | 最简单 | 对特殊字符脆弱,易出错 |
| 不同分隔符 | 日常推荐,变量包含 | 简单有效,解决大部分问题 | 对包含分隔符本身的变量可能失效 |
printf + here-string |
最健壮,变量包含任意特殊字符 | 绝对安全,万无一失 | 语法复杂,不易记忆 |
awk 替代 |
简单替换,追求可读性 | 语法清晰,变量处理直观 | 比 sed 稍重,非纯 sed 方案 |
实践建议:
- 首选方法:总是尝试使用不同的分隔符(方法二),这是
sed使用变量的标准技巧。 - 脚本中:在编写需要长期维护的脚本时,如果变量来源不可控(比如来自用户输入或文件),强烈推荐使用
printf+here-string(方法三)来确保绝对安全。 - 简单任务:如果只是做一两次简单的、可控的替换,双引号(方法一)就足够了。
- 权衡考虑:如果发现
sed命令因为变量问题变得异常复杂,不妨考虑一下是否用awk(方法四)能更优雅地解决问题。
