Bash 将变量内的引号读取为文本,而不是引号? Bash 中有“隐式引用”吗?

Bash 将变量内的引号读取为文本,而不是引号? Bash 中有“隐式引用”吗?

我有一个 bash 脚本可以定期清理邮件队列。出于某些原因,我们选择删除所有发送至 @mms.att.net 和其他 email2SMS 网关且在队列中超过 9 小时但仍未送达的电子邮件。

简单来说,该脚本执行以下操作:

domains=`cat /etc/mail/email2textdomains.txt`
egrep $domains /var/log/maillog | .... other tasks

的内容/etc/mail/email2textdomains.txt正是

"mms.att.net|vtxt.com|vtext.com|vzwpix.com"

因此,egrep 行应该是这样的,这正是我在命令行中输入的内容。

egrep "mms.att.net|vtxt.com|vtext.com|vzwpix.com" file | ...

如果我像这样运行它,那么它是一个 5 个以上阶段的命令管道,每个命令从前一个标准输出读取标准输入。这显然不是我想做的搜索。

egrep  mms.att.net|vtxt.com|vtext.com|vzwpix.com  file | ...

然而,在运行时,两个双引号的处理方式不同 - 它们成为字符串的一部分,所以我们本质上是在搜索

  • “mms.att.net
  • vtxt.com
  • vtext.com
  • vzwpix.com”

显然,我误解了引用的工作原理 - 解决方案是更改包含的行以删除双引号,导致一行不应该工作,但可以。

我尝试通过管道进行测试,od -a不显示任何非打印字符。

为什么它有效,使得内容/etc/mail/email2textdomains.txt正是

mms.att.net|vtxt.com|vtext.com|vzwpix.com

什么时候应该像所写的那样是一个很长的失败管道?

答案1

尝试调试此类事情时,一个很棒的工具是set -x.使用它,我们可以准确地看到您的命令正在做什么:

$ set -x
$ domains=$(cat domains.txt)
++ cat domains.txt
+ domains='"mms.att.net|vtxt.com|vtext.com|vzwpix.com"'

如您所见,$domains包括引号。因此,当您将其与 一起使用时grep,您会得到:

$ grep -E -- "$domains" file
+ grep --color -E -- '"mms.att.net|vtxt.com|vtext.com|vzwpix.com"' file

您想要做的是在 shell 级别使用引号,数据被传递给grep命令,但由于引号是变量数据的一部分,因此它们被像任何其他字符一样对待。最简单的解决方案是从文件中删除引号,然后仅引用变量,这无论如何都是最佳实践:

domains=$(tr -d \" < domains.txt) &&
grep -E -- "$domains" file

顺便说一句, usingvar=$(command)比 using 更受欢迎,var=`command`因为前者更清晰并且允许更多嵌套,并且egrep不推荐使用grep -E

另请注意,这.是一个匹配任何单个字符的正则表达式运算符,因此实际上会找到包含后跟任何单个字符、后跟任何单个字符、后跟 的grep mms.att.net行。例如,它也会匹配包含.mmsattnethammstattinet.com

因此,要构建一个与E包含任何这些域的行相匹配的扩展正则表达式,您不仅需要删除 s ",还要转义域名中恰好也是正则表达式运算符的所有字符。对于有效域名,应限制为..

另请注意,对于空正则表达式,不同实现的行为有所不同grep,但其中许多会报告所有行,因此您可能需要对其进行特殊处理。

所以:

regex=$(
  sed 's/"//g; # remove all "s like with tr
       s/\./\\./g; # substitute .s with \.s
      ' domains.txt
) && 
  [ -n "$regex" ] && # check it's not empty 
  grep -E -- "$regex" file

或者,您可以将|s 替换为换行符,并使用(以前的)-F选项来查找固定字符串:grepfgrepF

domains=$(<domains.txt tr -d '"' | tr '|' '\n') &&
  [ -n "$domains" ] &&
  grep -F -- "$domains" file

答案2

@Kaz 应该写下他的评论,以便它可以成为可接受的答案。

如果你想避免那么eval我认为你应该重写你的代码以放入额外的引号。我过于简单化的规则是每个美元符号都应该放在双引号内,除非你更了解。

我将更改为/etc/mail/email2textdomains.txt每行一个域,以利用 grep 允许换行符作为表达替代项的一种方式这一事实,即

mms.att.net
vtxt.com
vtext.com
vzwpix.com

并说

domains="$(cat /etc/mail/email2textdomains.txt)"
grep -- "$domains" /var/log/maillog | .... other tasks

引号仅位于第一行以满足我的规则,不需要它们。这--是为了防止-文本域文件中出现前导。使用直grep而不是egrepgrep -E来增加可移植性。实际上你正在写

grep -- "mms.att.net
vtxt.com
vtext.com
vzwpix.com" /var/log/maillog | .... other tasks

相关内容