我有一个 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
行。例如,它也会匹配包含.mms
att
net
hammstattinet.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
选项来查找固定字符串:grep
fgrep
F
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
而不是egrep
或grep -E
来增加可移植性。实际上你正在写
grep -- "mms.att.net
vtxt.com
vtext.com
vzwpix.com" /var/log/maillog | .... other tasks