使用sed在shell脚本中替换特殊字符

6
我正在尝试编写一份Shell脚本,使用sed命令替换我选择的任意字符/字符串。我的第一次尝试已经成功了,但是碰到了特殊字符的问题。我一直在尝试使用sed来解决特殊字符的问题,以便对其进行搜索或替换。为测试目的简化脚本后,只处理一个有问题的字符。然而,我仍然遇到了问题。
编辑后的脚本:
#! /bin/sh
oldString=$1
newString=$2
file=$3

oldStringFixed=$(echo "$oldString" | sed 's/\\/\\\\/g')
oldStringFixed=$(echo "$oldStringFixed" | sed 's/\[/\\\[/g')
oldStringFixed=$(echo "$oldStringFixed" | sed 's/\]/\\\]/g')
oldStringFixed=$(echo "$oldStringFixed" | sed 's/\^/\\\^/g')
oldStringFixed=$(echo "$oldStringFixed" | sed 's/\*/\\\*/g')
oldStringFixed=$(echo "$oldStringFixed" | sed 's/\+/\\\+/g')
oldStringFixed=$(echo "$oldStringFixed" | sed 's/\./\\\./g')
oldStringFixed=$(echo "$oldStringFixed" | sed 's/\$/\\\$/g')
oldStringFixed=$(echo "$oldStringFixed" | sed 's/\-/\\\-/g')

sed -e "s/$oldStringFixed/$newString/g" "$file" > newfile.updated
mv newfile.updated "$file"#! /bin/sh

如果不清楚的话,我正在尝试在 oldString 中搜索 [ 字符,并用转义版本替换它,然后将结果分配给 oldStringFixed(这需要加上反引号吗?)。最后两行是我原始脚本的稍微修改版本,我相信它们能正确工作。
当我 echo 固定的字符串时,没有显示任何内容,并且 sed 输出了错误。
sed: can't read [: No such file or directory

有人能解释一下我第一行sed命令错在哪里吗?

编辑:

感谢Jite,脚本现在运行得更好了。但是我仍然无法用空格替换单引号字符,即' *'。 新版本如上所示。


有很多比 "[" 更特殊的字符,比如对于 OldString,会有 . \ * + ?,而对于 newString,会有 \ & /。 - NeronLeVelu
与此问题相当类似:http://stackoverflow.com/questions/20427289/quoting-special-characters-with-sed/20427706。 - devnull
5个回答

3
我建议两个改进:
  1. 不要像你现在这样堆叠调用sed,而是将所有调用都打包到一个单独的函数中,如下所示的escape_string

  2. 您可以使用精美的分隔符来替换sed命令,以避免与涉及字符串的/相关的问题。

经过这些更改,您的脚本看起来像:

#! /bin/sh
oldString="$1"
newString="$2"
file="$3"

escape_string()
{
   printf '%s' "$1" | sed -e 's/[][\\^*+.$-]/\\\1/g'
}

fancyDelim=$(printf '\001')

oldStringFixed=$(escape_string "$oldString")
sed -e "s$fancyDelim$oldStringFixed$fancyDelim$newString${fancyDelim}g" "$file" \
  > newfile.updated
mv newfile.updated "$file"

1
你可以轻松地推广到s/[][\\^*+.$-]/\\\1/g(我不确定为什么你想包括连字符,所以建议将其删除)。 - tripleee
太好了,我已经添加了你的更改! - The Show that never ends
2
这对我不起作用,我得到了“sed:-e表达式#1,char 21:在`s'命令的RHS上无效的引用\1”。 - Ben Farmer
@BenFarmer 当字符串中没有特殊字符时,此方法无效。如果您的普通字符串不包含任何特殊字符,则 \1(即匹配项 1)将没有值,因此此方法将失败。如果值可能不包含匹配项,则无法使用枚举匹配项。 - Kim Gentes

3

如果要替换包含特殊字符的值,请尝试使用带有“|”的sed而不是“/”

例如:sed -i 's|'$original_value'|'$new_value'|g'

其中 original_value="comprising_special_char_/" new_value="comprising_new_special_char:"


2

更改:

oldStringFixed= `sed 's/\[/\[/g' "$oldString"\`

to:

oldStringFixed=$(echo "$oldString" | sed 's/\[/\\\[/g')

问题1:在分配shell变量时,=后面的空格是不允许的。
问题2:sed期望输入文件而不是字符串。您可以像我的解决方案一样使用管道。
问题3:您需要先转义反斜杠\\,然后需要转义您的字符\[,总共为\\\[ :)
备注:我将``更改为$(),因为后者是推荐的做法(由于嵌套,另一个主题)。

谢谢您的帮助。您的更改非常有效,我已经添加了其他sed语句来处理其他问题字符。 但是,当我尝试用空格替换单引号字符(例如'*'),输出结果不正确。 有没有什么办法可以解决这个问题? - user3074091
一切都可以解决。坦白地说,对于这种情况,我会编写一个名为 sed.script 的文件,其中包含要执行的所有命令,每行一个,然后只使用 sed -f sed.script 一次,而不是多次调用 sed。这也意味着您不必针对 shell 转义脚本-这使生活更轻松。 - Jonathan Leffler
@user3074091,我不明白你的问题。请在你的问题中更新一个示例输入和你期望的输出,希望我们能够给出一个答案。 - Jite

0

您可以使用以下命令将 " 替换为 \": sed 's/\"/\\\"/g' 文件名


0

对我来说,尝试让sed处理一般情况只是一场噩梦。最终我放弃了,写了一个简短的Python代码来替代sed:

#!/usr/bin/python
# replace.py
import sys

# Replace string in a file (in place)
match=sys.argv[1]
replace=sys.argv[2]
filename=sys.argv[3]

print "Replacing strings in",filename

with open(filename,"r") as f:
  data = f.read().replace(match,replace)

with open(filename,"w") as f:
  f.write(data)

然后可以像这样使用:

#!/bin/bash
orig='<somethinghorrible>'
out='<replacement>'
python replace.py "$orig" "$out" myfile.txt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接