【问题标题】:Escape dollar sign in regexp for sed在 sed 的正则表达式中转义美元符号
【发布时间】:2016-06-01 02:02:33
【问题描述】:

我将在实际提问之前介绍我的问题 - 请随意跳过本节!

关于我的设置的一些背景信息

为了在软件系统中手动更新文件,我正在创建一个 bash 脚本以使用 diff 删除新版本中不存在的所有文件:

for i in $(diff -r old new 2>/dev/null | grep "Only in old" | cut -d "/" -f 3- | sed "s/: /\//g"); do echo "rm -f $i" >> REMOVEOLDFILES.sh; done

这很好用。但是,显然我的文件的文件名中通常有一个美元符号 ($),这是由于 GWT 框架的一些排列。这是上面创建的 bash 脚本的一个示例行:

rm -f var/lib/tomcat7/webapps/ROOT/WEB-INF/classes/ExampleFile$3$1$1$1$2$1$1.class

执行此脚本不会删除想要的文件,因为 bash 将这些文件作为参数变量读取。因此,我必须用“\$”转义美元符号。

我的实际问题

我现在想在上述管道中添加一个 sed-Command,替换这个美元符号。事实上,sed 也将美元符号读取为正则表达式的特殊字符,所以显然我也必须将其转义。 但不知何故,这不起作用,在谷歌搜索后我找不到解释。

以下是我尝试过的一些变体:

echo "Bla$bla" | sed "s/\$/2/g"        # Output: Bla2
echo "Bla$bla" | sed 's/$$/2/g'        # Output: Bla
echo "Bla$bla" | sed 's/\\$/2/g'       # Output: Bla
echo "Bla$bla" | sed 's/@"\$"/2/g'     # Output: Bla
echo "Bla$bla" | sed 's/\\\$/2/g'      # Output: Bla

此示例中所需的输出应为“Bla2bla”。 我错过了什么? 我正在使用 GNU sed 4.2.2

编辑

我刚刚意识到,上面的例子一开始是错误的 - echo 命令已经将 $ 解释为一个变量,而下面的 sed 无论如何都没有得到它......这里是一个正确的例子:

  1. 创建一个文本文件test,内容为bla$bla
  2. cat testbla$bla
  3. cat test | sed "s/$/2/g"bla$bla2
  4. cat test | sed "s/\$/2/g"bla$bla2
  5. cat test | sed "s/\\$/2/g"bla2bla

因此,最后一个版本就是答案。记住:在测试的时候,首先要确保你的测试是正确的,然后再质疑测试对象......

【问题讨论】:

  • echo "Bla\\\$bla" | sed "s/\\\\\\$/2/g"。我想如果实际字符串包含 $ 作为字符串文字的一部分,它会起作用。
  • 不要使用for 遍历文件(或命令输出)。 mywiki.wooledge.org/BashFAQ/001
  • 我对这个问题投了赞成票,因为它是如何提出一个好问题的一个很好的例子:展示努力、研究并解释具体问题如何适应总体目标。欢迎来到 Stack Overflow,船长。
  • @AnthonyGeoghegan 谢谢 :-)
  • @WiktorStribiżew 更改实际字符串是我的目标 - 不是解决方案...

标签: regex bash shell sed


【解决方案1】:

在 sed 的正则表达式中转义美元符号的正确方法是 双反斜杠。然后,为了在输出中创建转义版本,我们需要一些额外的斜杠:

cat filenames.txt | sed "s/\\$/\\\\$/g" > escaped-filenames.txt

是的,这是连续四个反斜杠。这将创建所需的更改:像bla$1$2.class 这样的文件名将更改为bla\$1\$2.class。 然后我可以将其插入到完整的管道中:

for i in $(diff -r old new 2>/dev/null | grep "Only in old" | cut -d "/" -f 3- | sed "s/: /\//g" | sed "s/\\$/\\\\$/g"; do echo "rm -f $i" >> REMOVEOLDFILES.sh; done

替代解决后台问题

chepner 发布了解决背景问题的替代方法,只需在输出的文件名周围添加单引号。这样,bash 在执行脚本时不会将 $-signs 读取为变量,并且文件也会被正确删除:

for i in $(diff -r old new 2>/dev/null | grep "Only in old" | cut -d "/" -f 3- | sed "s/: /\//g"); do echo "rm -f '$i'" >> REMOVEOLDFILES.sh; done

(请注意该行中已更改的echo "rm -f '$i'"

【讨论】:

  • 否 -- 在 shell 的双引号字符串中 转义美元符号的正确方法是添加两个反斜杠。在单引号中,一个反斜杠是正确且足够的,两个反斜杠是错误的。通常,除非您需要 shell 插入变量并执行命令替换,否则请使用单引号。尽可能使用单引号。
【解决方案2】:

您的脚本还有其他问题,但如果您在生成的脚本中正确引用rm 的参数,则包含$ 的文件名不是问题。

echo "rm -f '$i'" >> REMOVEOLDFILES.sh

或者使用printf,这使得引用更好一点并且更便携:

printf "rm -f '%s'" "$i" >> REMOVEOLDFILES.sh

(请注意,我正在解决真正的问题,不一定是您提出的问题。)

【讨论】:

  • 感谢您的好主意,也感谢您解决我的实际问题 :-) 不幸的是,bash 仍会将“$1”之类的内容作为参数读取,即使它在引号内。所以这无济于事......
  • 我不确定这会发生在哪里。暴露给 shell 的文本中没有美元符号,只有在管道的输出中。在您发布的所有尝试中,您需要 echo 'Bla$bla' | sed ... 以便在 echo 甚至运行之前不会扩展 $bla,但您不需要处理初始管道的输出。
  • 也许这是一种误解 - 文件的名称中有 $-符号,并且肯定需要在输出 bash 脚本中列出。对于您的版本,它们只是在引号内。但是当我执行该脚本(真正删除文件)时,所有未转义的 $ 符号都被读取为变量 - 因为脚本在没有参数的情况下运行,它们只是扩展为一个空字符串。然后文件“bla$1.class”和“bla$1$2.class”都将被翻译成rm命令的“bla.class”
  • 哦,对不起,对。只需将输出中的双引号更改为单引号即可REMOVEOLDFILE.sh
【解决方案3】:

编辑后的问题中已经有一个很好的答案,对我有很大帮助 - 谢谢!

我只想添加一些我偶然发现的奇怪行为:匹配行尾的美元符号(例如,在修改 .bashrc 文件中的 PS1 时)。 作为一种解决方法,我匹配额外的空格。

$ DOLLAR_TERMINATED="123456 $"
$ echo "${DOLLAR_TERMINATED}" | sed -e "s/ \\$/END/"
123456END
$ echo "${DOLLAR_TERMINATED}" | sed -e "s/ \\$$/END/"
sed: -e expression #1, char 13: Invalid back reference
$ echo "${DOLLAR_TERMINATED}" | sed -e "s/ \\$\s*$/END/"
123456END

逐行解释:

  • 定义 DOLLAR_TERMINATED - 我想将 DOLLAR_TERMINATED 末尾的美元符号替换为“END”
  • 如果我不检查行尾,它会起作用
  • 如果我也匹配行尾(在左侧再添加一个 $),它将不起作用
  • 如果我另外匹配(不存在的)空格,它会起作用

(我的 sed 版本是 4.2.2,从 2016 年 2 月开始,bash 版本是 4.3.48(1)-release (x86_64-pc-linux-gnu),以防有什么不同)

【讨论】:

  • 起初我以为你的第一个回声123456END 缺少$,因为回声吃了它。用撇号试试DOLLAR_TERMINATED='123456 $',用echo ${DOLLAR_TERMINATED} 测试以确保它是正确的。
  • 您的第二个回显出现异常,因为 $$ 被解释为您的 PID(一个数字)并且 sed 没有找到具有该反向引用号的 (pattern)。您可以通过echo "$$" 进行检查
  • 您也可以使用echo "${DOLLAR_TERMINATED}" | sed -e "s/ \\\$$/END/" 实现此目的。三个反斜杠是两个用于转义反斜杠,一个用于转义美元。或 echo "${DOLLAR_TERMINATED}" | sed -e 's/ \$$/END/' 使用撇号,因此外壳不需要转义符来传递您想要的 sed。
猜你喜欢
  • 2018-01-16
  • 1970-01-01
  • 2021-01-04
  • 2022-12-04
  • 1970-01-01
  • 2012-03-31
  • 1970-01-01
  • 2019-05-11
  • 2020-03-08
相关资源
最近更新 更多