在bash脚本中,sed命令无法正常工作

5
我已经阅读了关于这个主题的所有类似问题,但没有找到符合我所经历的问题。如果已经有答案,请原谅。
在我编写的bash脚本中,有一个非常简单的sed命令,但它似乎不起作用。没有错误,并且当从命令行运行该命令时,该命令完美地工作。
在set -x的输出中,我可以看到sed命令完美执行。
GNU bash,版本4.3.11(1)-release(x86_64-pc-linux-gnu)
Bash脚本:(为了更容易理解而减弱了语气)
#!/bin/bash -x

# This script has the exact same sed command as used on cli

contact='"tim@email.com"'

sed -i "/$contact/d" /home/tim/Desktop/file.txt

exit

Shell输出:

tim@ubuntu:~/Desktop$ cat file.txt
t,b,tim@email.com
tim@ubuntu:~/Desktop$ ./test.sh 
+ contact='"tim@email.com"'
+ sed -i '/"tim@email.com"/d' /home/tim/Desktop/file.txt
+ exit
tim@ubuntu:~/Desktop$ cat file.txt
t,b,tim@email.com
tim@ubuntu:~/Desktop$ sed -i "/"tim@email.com"/d" /home/tim/Desktop/file.txt
tim@ubuntu:~/Desktop$ cat file.txt
tim@ubuntu:~/Desktop$

我想我可能忽略了一些非常显而易见的东西,但我已经盯着屏幕看了很久,希望答案能跳出来打在我的脸上。请帮帮我 :-) Tim

你的命令不一样:在脚本中,你用单引号括起来匹配模式,在cli中则用双引号。这意味着,从你的bash脚本调用sed时,它会匹配双引号括起来的电子邮件地址,而在你的文件中并不是这样。 - collapsar
@anubhava 抱歉,我应该猜到有人会问这个问题。在简化的脚本中似乎没有意义,但在实际脚本中有一个base64解码的字符串,然后进行openssl解密并成为$contact变量,并被引用。我宁愿不尝试删除引号,因为该变量在许多其他函数中使用,这些函数将不得不重写。无论如何,在命令行上使用带引号的方式可以正常工作。 - asimovwasright
@collapsar 我也是这么想的,只不过在脚本中没有单引号,这似乎只发生在 set -x 输出中。 - asimovwasright
它在命令行中使用单引号运行吗? - collapsar
在脚本中,你的变量包含邮件地址周围的双引号 - 这些字符在你的文件中缺失。这就是为什么它无法匹配的原因。 - collapsar
显示剩余8条评论
2个回答

9

$contact脚本变量中的邮件地址周围有双引号,在命令行调用中缺失:

# case 1 - works
# only the sed pattern delimiters are enclosed in quotes and these quotes will be stripped by the shell. 
sed -i "/"tim@email.com"/d" ./file.txt; cat file.txt

# case 2 - fails
# escaping with \ turns dquotes #2,3 from shell-level delimiters to char literals w/o special  semantics.
sed -i "/\"tim@email.com\"/d" ./file.txt; cat file.txt

# case 3 - fails
# Single quotes enclose the complete sed pattern spec which comprises double quotes enclosing the mail address
sed -i '/"tim@email.com"/d' ./file.txt; cat file.txt

# case 4 - works
sed -i "/tim@email.com/d" ./file.txt; cat file.txt

# case 5 - works
sed -i '/tim@email.com/d' ./file.txt; cat file.txt

这可以解释脚本和cli调用的不同行为。
原帖作者指出他在真实脚本中需要双引号。尽管如此,如果文件中没有这些双引号,则不会有匹配。
一种解决方法是使用sed预处理文件(如果需要,可以使用副本):
sed -i 's/,/","/g; s/^/"/; s/$/"/' ./file.txt

这个命令假设每行都有一个逗号分隔的项目列表,没有任何项目包含双引号。它会将每个项目用双引号括起来,以便与原始脚本的$contact变量中的搜索模式匹配。 另一种选择(改编自此 SO 回答 [我不是作者])
另一个选项是从$contact派生出第二个变量来更改脚本的相关部分:
contact='"tim@email.com"'
c2=$(echo $contact | tr -d '"')

sed -i "/$c2/d"  /home/tim/Desktop/file.txt

为什么双引号会被 shell 剥离并替换成单引号?如果我能理解这个问题,我会非常高兴 :-) - asimovwasright
1
引用是一种通用机制,用于指定包含具有特殊语义的字符(例如空格)的字符串文字到命令处理器中。双引号和单引号不同(作为经验法则),因为双引号仍然允许变量扩展。因此,在任何情况下,sed 都无法看到最外层的引号对。在您的命令行调用中,有 2 个最外层的引号对和有效地包装成单个 sed 参数(模式表达式)的 3 个相邻字符串。我已添加了第二种情况,与第 1、3 种情况进行对比。 - collapsar
好的,明白了,非常感谢您提供如此清晰的解释!我想我将不得不使用您的预处理器来更改匹配条件。非常有帮助 :-) - asimovwasright

3
这是对collapsar答案的补充,该答案已经解决了问题。
当我们在Bash脚本中使用sed时,Bash脚本充当sed的包装器。这有两个目的:
- 可以像在Bash之外一样执行sed命令。 - Bash脚本包装器帮助sed使用环境变量与外界通信。
例如,假设文件testfile包含两行。
somebody@email.com
tim@email.com

现在,如果我想编写一个bash脚本来帮助sed替换包含tim@email.com的行,我的脚本sedscript如下所示:
#!/bin/bash
contact='tim@email.com'
sed -i "/$contact/d" $1

现在我会像下面这样执行脚本。
./sedscript testfile

删除所有包含tim@email.com的行。

实际上,您可以将$1替换为实际文件名。但是需要注意的重要一点,正如前面的答案中提到的那样,每当我们在sed命令中使用bash变量时,总是要用双引号括起来。只有这样,bash才会在传递给sed之前用相应的字符串替换变量。


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