有没有办法防止sed解释替换字符串?[关闭]


16

如果要使用sed将关键字替换为字符串,sed会尝试解释您的替换字符串。如果替换字符串碰巧包含sed认为特殊的字符(例如'/'字符),则它将失败,除非您当然是要让替换字符串包含告诉sed如何操作的字符。

例如:

VAR="hi/"

sed "s/KEYWORD/$VAR/g" somefile

有没有办法告诉sed不要尝试解释特殊字符的替换字符串?我想要的是能够用变量的内容替换文件中的关键字,无论该内容是什么。


如果要放入特殊字符sed而不让它们特殊,则只需反斜杠即可将其转义。 VAR='hi\/'没有任何问题。
通配符

6
为什么所有的反对票?对我来说,这似乎是一个完全合理的问题
roaima

sed(1)只是解释它会得到什么。在您的情况下,它是通过外壳插值获得的。我相信您无法按照自己的意愿做,但请查阅手册。我知道在Perl中(这是一个可以通过的sed替代方法,具有更丰富的正则表达式),您可以指定要从字面上接受的字符串,再次检查一下手册。
vonbrand '16

Answers:


4

有4只在更换部分特殊字符:\&,换行和定界符(REF

$ VAR='abc/def&ghi\foo
next line'

$ repl=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$VAR")

$ echo "$repl"
abc\/def\&ghi\\foo\
next line

$ echo ZYX | sed "s/Y/$repl/g"
Zabc/def&ghi\foo
next lineX

这具有与Antti解决方案相同的问题-如果替换字符串超过一定长度,则会出现“参数列表过长”错误。另外,如果替换字符串包含“ [”,“]”,“ *”,“。”以及其他此类字符,该怎么办?sed真的不会解释这些吗?
2016年

的更换侧s///不是一个正则表达式,它实际上只是一个字符串(除了反斜杠逃逸和&)。如果替换字符串很长,那么解决方案不是使用单层外壳。
格伦

一个非常有用的列表,例如,如果您的替换字符串是base64编码的文本(例如,用SHA256键替换占位符)。这只是担心的分隔符。
Heath Raftery

4

您可以使用Perl代替sed与-p(假设输入循环)和-e(在命令行上提供程序)。使用Perl,您可以访问环境变量而无需在shell中内插这些变量。请注意,该变量需要导出

export VAR='hi/'
perl -p -e 's/KEYWORD/$ENV{VAR}/g' somefile

如果您不希望将变量导出到任何地方,则只需为该过程提供变量即可:

PATTERN="$VAR" perl -p -e 's/KEYWORD/$ENV{PATTERN}/g' somefile

请注意,Perl的正则表达式语法默认情况下与sed略有不同。


这似乎很有希望,但是在测试时,由于替换字符串太长,我会收到“参数列表太长”错误,这很有意义-使用此方法,我们将整个替换字符串用作给出的参数的一部分到perl,因此它可以持续多长时间受到限制。
2016年

1
不,它将进入PATTERN 环境变量,而不是参数。在任何情况下,此错误都是E2BIG,如果使用,您将同样得到该错误sed
Antti Haapala

2

仍然可以正确处理绝大多数变量值的最简单解决方案是,使用非打印字符作为seds替代命令的定界符。

在其中,vi您可以通过输入Ctrl-V来转义任何控制字符(通常写为^V)。因此,如果您使用某些控制字符(^A在这些情况下,我经常将其用作分隔符),则sed仅当您要插入的变量中存在该非打印字符时,您的命令才会中断。

因此,您将键入"s^V^AKEYWORD^V^A$VAR^V^Ag"内容,您将获得(在中vi)如下所示:

sed "s^AKEYWORD^A$VAR^Ag" somefile

只要$VAR不包含非打印字符^A,这将起作用—这是极不可能的。


当然,如果要将用户输入传递到的值$VAR,则所有选择都将关闭,并且最好彻底清除输入内容,而不要依赖于普通用户难以键入的控制字符。


但是,实际上要注意的不仅仅是分隔符字符串。例如,&当出现在替换字符串中时,表示“匹配的整个文本”。例如,s/stu../my&/将“ stuff”替换为“ mystuff”,将“ stung”替换为“ mystung”,依此类推。因此,如果您要在变量中包含任何字符作为替换字符串,但您想使用文字值,那么您需要对数据进行清理,然后才能在中将变量用作替换字符串sed。(数据消毒是可以做到的sed也,虽然)。


我的意思是-用另一个字符串替换一个字符串是一个非常简单的操作。真的需要弄清楚sed不喜欢哪些字符,然后使用sed清理其自身输入内容吗?这听起来很荒谬,而且不必要。我不是专业程序员,但是我很确定我可以编写一个小函数,用几乎我遇到过的任何语言(包括bash)用字符串替换关键字,我只是希望使用一个简单的Linux使用现有工具的解决方案-我简直不相信那里没有。
2016年

1
@Tal,如果您的替换字符串是“ 100页的长度”(如您在另一条评论中所述),则几乎无法将其称为“简单”用例。顺便说一下,这里的答案是Perl,我只是还没学过Perl。这里的复杂性来自以下事实:您想在regex中允许任何任意输入作为替换字符串
通配符

您可以使用许多其他解决方案,其中许多非常简单。举例来说,如果您的替换字符串实际上是基于行的,也不需要在被插入中间的一条线,使用sedinsert命令。但是,sed它不是以复杂方式处理大量文本的好工具。我将发布另一个答案,说明如何使用awk
通配符

1

您可以使用a ,或a |代替它,它将其用作分隔符,从技术上讲,您可以使用任何东西

从手册页

\cregexpc
           Match lines matching the regular expression regexp.  The  c  may
      be any character.

如您所见,您应该在分隔符之前以\开头,然后可以将其用作分隔符。

来自文档http://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command

The / characters may be uniformly replaced by any other single character 
within any given s command.

The / character (or whatever other character is used in its stead) can appear in 
the regexp or replacement only if it is preceded by a \ character.

例:

sed -e 'somevar|s|foo|bar|'
echo "Hello all" | sed "s_all_user_"
echo "Hello all" | sed "s,all,user,"

echo "Hello/ World" | sed "s,Hello/,Neo,"


您正在谈论允许在替换字符串中使用单个特定字符-在这种情况下为“ /”。我说的是防止它试图完全解释替换字符串。无论您使用什么字符(“ /”,“,”,“ |”等),您总是冒着在替换字符串中弹出该字符的风险。另外,首字母不是sed关心的唯一特殊字符,不是吗?
2016年

@Tal不,它可以代替任何东西,/并且/正如我刚刚指出的那样,它将幸福地忽略..实际上,您甚至可以查找它并将其替换为字符串>>>我已经用示例编辑过>>>这些东西不是那么安全,您总会找到一个更聪明的家伙
user3566929

@Tal为什么要阻止它解释?我的意思是sed首先使用,您的项目是什么?
user3566929 '16

我所需要做的就是用一个字符串替换一个关键字。到目前为止,sed似乎是最常用的方法。该字符串可以是100页长。我不想尝试对字符串进行清理,以使sed在读取时不会出现异常-我希望它能够处理字符串中的任何字符,并且通过“处理”,我的意思是不要试图寻找神奇的东西内在的意思。
2016年

1
@Tal,bash不是对字符串操作。完全没有。它用于文件操作命令协调。它恰好具有一些内置的字符串方便功能,但是如果这是您要做的主要事情,则确实非常有限且根本不是很快。请参阅“为什么使用shell循环处理文本被视为不良做法?” 从最基本的到最强大的, 一些用于文本处理的工具:和Perl。sedawk
通配符

1

如果它是基于行的,并且只能替换一行,那么我建议使用替换行在文件本身前添加printf,将第一行存储在sed的保留空间中,然后根据需要将其放入。这样,您完全不必担心特殊字符。(这里唯一的假设是只$VAR包含一行文本,没有任何换行符,这就是您在注释中已经说过的内容。)除了换行符之外,VAR可以包含任何内容,并且无论如何都可以使用。

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/KEYWORD/g'

printf '%s\n'$VAR不管内容如何,​​都将以文本字符串的形式打印的内容,后跟换行符。(echo在某些情况下会做其他事情,例如,如果的内容$VAR以连字符开头-它将被解释为传递给的选项标志echo。)

大括号用于将输出printf的内容放在somefile传递给之前sed。分隔花括号本身的空白在这里很重要,右花括号前的分号也很重要。

1{h;d;};作为sed命令,会将文本的第一行存储在sedhold空间中,然后d删除该行(而不是打印它)。

/KEYWORD/对包含的所有行应用以下操作KEYWORD。动作是get,它将获取保留空间的内容并将其放置在模式空间(即整个当前行)的位置。(这并不是仅替换一行的一部分。)顺便说一句,保留空间并没有被清空,只是被复制到模式空间中,替换了其中的任何内容。

如果您想锚定正则表达式,以使其不匹配仅包含 KEYWORD 的行,而仅包含 KEYWORD的行,则在行^末尾添加行锚()和行锚($)到您的正则表达式:

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/^KEYWORD$/g'

如果您的VAR长一行,这似乎很棒。我实际上在注释中提到VAR“可以长100页”,而不是一行。对困惑感到抱歉。
2016年

0

您可以使用Bash的模式替换参数扩展来在替换字符串中反斜杠转义正斜杠。有点混乱,因为Bash也需要转义正斜杠。

$ var='a/b/c';var="${var//\//\\/}";echo 'this is a test' | sed "s/i/$var/g"

输出

tha/b/cs a/b/cs a test

可以将参数扩展直接放入sed命令中:

$ var='a/b/c';echo 'this is a test' | sed "s/i/${var//\//\\/}/g"

但我认为第一种形式更具可读性。当然,如果要在多个sed命令中重复使用相同的替换模式,则只需执行一次转换就可以了。

另一种选择是使用用awk,perl或Python编写的脚本或C程序代替您的sed进行替换。


这是Python中的一个简单示例,如果要替换的关键字是输入文件中的完整行(不计算换行符),则可以使用该示例。如您所见,它与Bash示例本质上是相同的算法,但是它可以更有效地读取输入文件。

import sys

#Get the keyword and replacement texts from the command line
keyword, replacement = sys.argv[1:]
for line in sys.stdin:
    #Strip any trailing whitespace
    line = line.rstrip()
    if line == keyword:
        line = replacement
    print(line)

这只是清理输入的另一种方法,并不是一个好方法,因为它只能处理一个特定字符('/')。正如Wildcard所指出的,要提防的不仅仅是分隔符字符串。
2016年

公平电话。例如,如果替换文本包含任何反斜杠转义的序列,则将对它们进行解释,这可能不是所希望的。一种解决方法是将有问题的字符(或整个字符)转换为\x样式转义序列。或者使用一个可以处理任意输入的程序,就像我在上一段中提到的那样。
下午16年

@Tal:我将在答案中添加一个简单的Python示例。
下午16年

python脚本工作得很好,并且似乎完全可以执行我的函数,只是效率更高。不幸的是,如果主脚本是bash(就我而言),则需要使用辅助外部python脚本。
2016年

-1

这是我走的路:

#Replaces a keyword with a long string
#
#This is normally done with sed, but sed
#tries to interpret the string you are
#replacing the keyword with too hard
#
#stdin - contents to look through
#Arg 1 - keyword to replace
#Arg 2 - what to replace keyword with
replace() {
        KEYWORD="$1"
        REPLACEMENT_STRING="$2"

        while IFS= read -r LINE
        do
                if [[ "$LINE" == "$KEYWORD" ]]
                then
                        printf "%s\n" "$REPLACEMENT_STRING"
                else
                        printf "%s\n" "$LINE"
                fi
        done < /dev/stdin
}

这对我来说非常有效,因为我的关键字本身就是一行。如果关键字与其他文字在同一行,则此方法将无效。

我仍然非常想知道是否有一种简单的方法可以完成,而无需编写自己的解决方案。


1
如果您真的担心特殊字符和健壮性,则完全不应该使用echo使用printf代替。在shell循环做文本处理是一个坏主意。
通配符

1
如果您在问题中提到关键字将始终是完整的行,将会很有帮助。FWIW,bash的read速度相当慢。它用于处理交互式用户输入,而不是文本文件处理。这很慢,因为它逐字符读取stdin字符,并对每个字符进行系统调用。
下午16年

@PM 2Ring我的问题没有提到该关键字是单独出现的,因为我不希望这样的答案仅在如此有限的情况下有效-我想要无论关键字在哪里都可以轻松使用的方法是。我也从来没有说过我的代码是有效的-如果可以的话,我不会在寻找替代方法……
Tal

@Wildcard除非我丢失了某些内容,否则printf绝对会解释特殊字符,并且比默认的“ echo”要好得多。printf "hi\n"将使printf在换行时打印换行符echo "hi\n"
2016年

@Tal,“ f”中的“ f” printf代表“格式”,第一个参数printf格式说明符。如果该说明符%s\n,意思是“字符串,然后换行”,没有在一个参数将被解释或翻译printf 所有。(当然,shell仍然可以解释它;如果它是文字字符串,最好将其全部用单引号括起来,如果要变量扩展则最好用双引号括起来。)有关更多详细信息,请参见我的回答printf
通配符
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.