【问题标题】:"sed" special characters handling"sed" 特殊字符处理
【发布时间】:2014-09-13 10:36:21
【问题描述】:

我们的脚本中有一个 sed 命令,用变量中的值替换文件内容

例如..

export value="dba01upc\Fusion_test"
sed -i "s%{"sara_ftp_username"}%$value%g" /home_ldap/user1/placeholder/Sara.xml

sed 命令会忽略特殊字符,如 '\' 并用不带 '\' 的字符串“dba01upcFusion_test”替换 如果我像 export value='dba01upc\Fusion_test' ('\' 用 '' 包围)那样进行导出,它可以工作。但不幸的是,我们的客户想要用单引号/双引号导出原始文本 dba01upc\Fusion_test 而他没有'不想在文本中添加任何额外的字符。 谁能告诉我如何让 sed 放置带有特殊字符的文本..

替换前:Sara.xml

<?xml version="1.0" encoding="UTF-8"?>
<ser:service-account >
<ser:description/>
<ser:static-account>
<con:username>{sara_ftp_username}</con:username>
</ser:static-account>
</ser:service-account>

替换后:Sara.xml

<?xml version="1.0" encoding="UTF-8"?>
<ser:service-account>
<ser:description/>
<ser:static-account>
<con:username>dba01upcFusion_test</con:username>
</ser:static-account>
</ser:service-account>

提前致谢

【问题讨论】:

  • 仅供参考,BashFAQ #21 包含一个可用于可靠替换的 awk 脚本;见mywiki.wooledge.org/BashFAQ/021
  • 不需要导出不需要从子进程访问的变量......对于保存密码/凭证的变量,导出它们只是一个坏主意(它已修复在大多数非常新的UNIX 喜欢,但一些较旧的系统使环境变量以与命令行相同的方式公开可见)。
  • wrt 引用的 awk 脚本,如果您在 arg 列表中传递字符串而不是从中填充变量,则不需要转义反斜杠。无论如何,我不相信盲目地逃避每个反斜杠是安全的,但我可能是错的——有一天当我无事可做时,我实际上会尝试看看我是否能想出一个反例。
  • @CharlesDuffy:在链接的awk 脚本中,除了需要转义反斜杠之外,输入字符串中还存在实际换行符的问题,这在使用 BSD @987654327 时会破坏命令@ (据我了解,这种行为可能符合 POSIX)。在@Ed's answer 中使用传递字符串作为伪文件名技术可以避免这个问题,同时也避免了转义的需要。

标签: regex bash shell unix sed


【解决方案1】:

您无法使用 sed 稳健地解决此问题。只需使用 awk 代替:

awk -v old="string1" -v new="string2" '
idx = index($0,old) {
    $0 = substr($0,1,idx-1) new substr($0,idx+length(old))
}
1' file

啊,@mklement0 有一个好点子 - 要阻止转义符被解释,您需要传入 arg 列表中的值以及文件名,然后从中分配变量,而不是使用-v(请参阅我很久以前在http://cfajohnson.com/shell/cus-faq-2.html#Q24 为 comp.unix.shell 常见问题解答编写的摘要,但显然已经忘记了!)。

以下内容将有力地对每行找到的每个搜索字符串进行所需的替换 (a\ta -> e\tf):

$ cat tst.awk
BEGIN {
    old=ARGV[1]; delete ARGV[1]
    new=ARGV[2]; delete ARGV[2]
    lgthOld = length(old)
}
{
    head = ""; tail = $0
    while ( idx = index(tail,old) ) {
        head = head substr(tail,1,idx-1) new
        tail = substr(tail,idx+lgthOld)
    }
    print head tail
}

$ cat file
a\ta    a       a       a\ta

$ awk -f tst.awk 'a\ta' 'e\tf' file
e\tf    a       a       e\tf

file 中的空白是制表符。您可以根据需要将 ARGV[3] 向下移动并调整 ARGC,但在大多数情况下没有必要。

【讨论】:

  • 虽然这比@anubhava 的printf "%q" 解决方案更健壮,但它仍然与'\1' 等字符串中断,因为awk 如何解析字符串文字中的转义序列。您必须“预先转义”\ 实例才能解决此问题。
  • 好点,但解决方案是移动作业,而不是转义它们(你不能只转义反斜杠,因为它们可能已经转义了一些东西)。查看我更新的解决方案。
  • 我真的不确定是否盲目地转义\ s 是否有问题。总有一天,我会多考虑一下,看看我是否可以说服自己这没关系,或者证明它不是。当替换字符串中有文字换行符时,您的 sed 解决方案的行为对我来说并不理想 - 我得到了 sed: -e expression #1, char 30: unterminated s' command`。一般来说,我认为在替换字符串中转义字符比在搜索字符串中的问题要小得多,其中哪些字符应该是,哪些不能被转义是一个更大的集合,并且取决于您的 sed 版本和选项(例如 BRE 与 ERE)。
  • 我认为我在转义 RE 元字符(以及 sed 也使用分隔符和反向引用)以尝试使工具将给定字符串视为文字时遇到的主要问题是只是它在概念上是错误的方法。这就像想要一杯水,然后去冰箱里买冰块,然后用微波炉加热直到它们解冻,而不是简单地倒一杯水。我们想对字符串进行操作,所以让我们传入字符串并使用字符串函数/操作。所有这些关于尝试将字符串转换为“安全”RE 以便我们可以使用 RE 函数/操作的东西都是丑陋的。
  • 好点,尤其是重新转义 search 字符串。我的sed 解决方案必须跳过进一步支持您的观点,并且它仅涵盖 replacement 字符串。您修改后的解决方案现在确实是最强大和最通用的 - 并且更易于启动。
【解决方案2】:

更新事后诸葛亮,提出选项

  • 更新 2:如果您打算使用 sed请参阅下面的 - 有点繁琐,但现在强大且通用的 - 解决方案
  • 如果您想要一个稳健、自包含的awk 解决方案,该解决方案还可以正确处理任意搜索和替换字符串(但不能包含诸如单词边界断言之类的正则表达式功能),请参阅@987654321 @。
  • 如果您想要bash 解决方案并且您的输入文件很小并且保留多个尾随换行符并不重要,请参阅Charles Duffy's answer
  • 如果您想要一个成熟的第三方模板解决方案,例如,考虑j2cli,一个用于Jinja2 的模板CLI - 如果您有Python 和 pip,使用 sudo pip install j2cli 安装。
    简单示例(请注意,由于替换字符串是通过 file 提供的,因此这可能不适用于敏感数据;请注意 double 大括号 ({{...}})):

    value='dba01upc\Fusion_test'
    echo "sara_ftp_username=$value" >data.env
    echo '<con:username>{{sara_ftp_username}}</con:username>' >tmpl.xml
    j2 tmpl.xml data.env # -> <con:username>dba01upc\Fusion_test</con:username>
    

如果您使用sed,则需要仔细转义搜索字符串和替换字符串,因为:

  • 正如 Ed Morton 在其他地方的评论中指出的那样,sed 不支持将 literal 字符串用作替换字符串 - 它总是 解释 中的特殊字符/序列替换字符串。
  • 同样,search 字符串文字必须以不会将其字符误认为特殊正则表达式字符的方式进行转义。

以下使用两个通用帮助函数来执行此转义(引用),它们应用了"Is it possible to escape regex characters reliably with sed?" 中解释的技术:

#!/usr/bin/env bash

# SYNOPSIS
#   quoteRe <text>
# DESCRIPTION
#   Quotes (escapes) the specified literal text for use in a regular expression,
#   whether basic or extended - should work with all common flavors.
quoteRe() { sed -e 's/[^^]/[&]/g; s/\^/\\^/g; $!a\'$'\n''\\n' <<<"$1" | tr -d '\n'; }

# '

# SYNOPSIS
#  quoteSubst <text>
# DESCRIPTION
#  Quotes (escapes) the specified literal string for safe use as the substitution string (the 'new' in `s/old/new/`).
quoteSubst() {
  IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g' <<<"$1")
  printf %s "${REPLY%$'\n'}"    
}

# The search string.
search='{sara_ftp_username}'

# The replacement string; a demo value with characters that need escaping.
value='&\1%"'\'';<>/|dba01upc\Fusion_test'

# Use the appropriately escaped versions of both strings.
sed "s/$(quoteRe "$search")/$(quoteSubst "$value")/g" <<<'<el>{sara_ftp_username}</el>'

# -> <el>&\1%"';<>/|dba01upc\Fusion_test</el>
  • quoteRe()quoteSubst() 都能正确处理多行字符串
    • 但是请注意,鉴于sed 默认情况下一次读取 行,将quoteRe() 与多行字符串一起使用仅在显式读取的sed 命令中有意义一次多(或所有)行。
  • quoteRe() 总是安全地使用命令替换 ($(...)),因为它总是返回一个 单行 字符串(换行符)在输入中被编码为'\n')。
  • 相比之下,如果您将 quoteSubst() 与带有 尾随换行符的字符串一起使用,则不得使用 $(...),因为后者将删除最后一个尾随换行符并因此破坏编码(因为quoteSubst()\-转义实际换行符,返回的字符串将以悬空\结尾)。
    • 因此,对于带有尾随换行符的字符串,首先使用IFS= read -d '' -r escapedValue &lt; &lt;(quoteSubst "$value") 将转义值读入单独的变量,然后在sed 命令中使用该变量。

【讨论】:

  • +1 比我的更强大的解决方案(现已删除)
【解决方案3】:

这可以单独使用 bash 内置函数来完成 -- 没有 sed、没有 awk 等。

orig='{sara_ftp_username}'               # put the original value into a variable
new='dba01upc\Fusion_test'               # ...no need to 'export'!

contents=$(<Sara.xml)                    # read the file's content into
new_contents=${contents//"$orig"/$new}   # use parameter expansion to replace
printf '%s' "$new_contents" >Sara.xml    # write new content to disk

有关使用参数扩展进行字符串替换的信息,请参阅the relevant part of BashFAQ #100

【讨论】:

  • 没错,但一般需要注意的是它只适用于较小的文件,因为整个输入文件一次读取。此外,如果您希望 $orig 的内容被视为 literal,则必须将其双引号:new_contents=${contents//"$orig"/$new}(而您 can,但 不需要双引号$new)。
  • @mklement0,谢谢——我在那里学到了一些东西;以前并不知道 $orig 在这种情况下会被视为一种模式。
  • 请注意,这种方法会删除文件末尾的所有空白行,这可能是不可取的。
  • @EdMorton:好点;并且,为了澄清,由于“空白”可以解释为包含 all-whitespace 行,我们正在讨论尾随 empty 行(一个或多个换行符的运行在文件的最后)。注意到这一点,最好输出带有尾随换行符的修改后的字符串:printf '%s\n' ...
  • @mklement0 同意,如果不存在,我的 awk 脚本会添加一个换行符,但是在 gawk 中,至少你总是可以通过设置 ORS=RT 来选择是否添加它,如果那真的是问题。在其他 awks 中,您需要发挥创造力!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-17
  • 2011-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多