sed的正则表达式中的转义美元符号

问题描述:

我会在实际询问之前介绍我的问题 - 请随时跳过本节!sed的正则表达式中的转义美元符号

关于我的设置的一些背景资料

要在软件系统中手动更新文件,我创建一个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命令在上述管道,更换这个美元符号。事实上,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. 与内容创建一个文本文件testbla$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

因此,最后的版本是答案。记住:测试时,首先要确保您的测试是正确的,你的问题的测试对象之前........

+0

'回声 “布拉\\\ $喇嘛” | sed“s/\\\\\ $/2/g”'。我想如果实际的字符串包含一个'$'作为字符串文字的一部分,它会起作用。 –

+0

不要使用'for'来迭代文件(或命令输出)。 http://mywiki.wooledge.org/BashFAQ/001 – chepner

+0

我提出了这个问题,因为这是一个很好的例子:如何提出一个好问题:展示努力,研究和解释具体问题如何适应总体目标。欢迎来到Stack Overflow,队长。 –

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

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

或使用printf,这使得引用一点更好,更便于携带:

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

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

+0

感谢您的好主意,也解决了我的实际问题:-)不幸的是,bash仍然会读取类似“$ 1”作为参数,即使它在引号内。所以这没有帮助... –

+0

我不确定那里会发生什么。暴露给外壳的文本中没有美元符号,只在流水线的输出中显示。在你发布的所有尝试中,你都需要'echo'Bla $ bla'| sed ...'这样''bla'在'echo'甚至运行之前不会扩展,但是您不需要处理初始管道的输出。 – chepner

+0

也许这是一个误解 - 这些文件的名称中有$ -signs,并且肯定需要在输出bash脚本中列出。随着你的版本,他们只是在引号内。但是当我执行该脚本(真正删除文件)时,所有未转义的$符号都被读为变量 - 因为脚本运行时没有参数,它们只是展开为空字符串。然后,文件“bla $ 1.class”和“bla $ 1 $ 2.class”都将被翻译为“bla.class”,用于rm命令 –

在正则表达式中为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张贴替代简单地周围的文件名添加单引号解决底色问题为输出。通过这种方式,在执行脚本时的$ -signs不读如bash的变量和文件也正常删除:

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'"

+0

否 - 在shell中的双引号字符串中转义美元符号*的正确方法是放置两个反斜杠。在单引号中,单个反斜杠是正确且足够的,并且两个反斜杠是错误的。通常,除非你需要shell来插入变量并执行命令替换,否则使用单引号。尽可能使用单引号。 – tripleee

已经有一个在编辑的问题中直接给出了很好的答案,帮助我很多 - 谢谢!

我只是想添加一些我偶然发现的好奇行为:在行尾(例如,在您的.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”,以取代美元符号
  • ,如果我穿上它的工作原理” t检查行结尾
  • 如果我匹配行结尾以及在左边添加一个$
  • 它不起作用如果我另外匹配(不存在)空白

(我的sed版本是从2016年2月4.2.2,是bash 4.3.48(1)-release (x86_64-pc-linux-gnu)版本,万一有什么差别)