6

我完全迷失在 shell 编程中,主要是因为我使用的每个站点都提供了不同的工具来进行模式匹配。所以我的问题是使用什么工具在管道流中进行简单的模式匹配。

上下文:我有 named.conf 文件,我需要一个简单文件中的所有区域名称以进行进一步处理。所以我做~$ cat named.local | grep zone 并在这里完全迷失。我的输出是 'zone "domain.tld" {' 形式的约一百个换行符,我需要双引号中的文本。

感谢您展示了一种方法。

Ĵ

4

5 回答 5

24

我认为您正在寻找的是sed……它是一个编辑器,可让您逐行进行替换。

正如您所解释的那样,命令 `cat named.local | grep zone' 给你一个有点像这样的输出:

zone "domain1.tld" {
zone "domain2.tld" {
zone "domain3.tld" {
zone "domain4.tld" {

我猜你希望输出是这样的,因为你说你需要双引号中的文本:

"domain1.tld"
"domain2.tld"
"domain3.tld"
"domain4.tld"

因此,实际上,从每一行中,我们只需要双引号之间的文本(包括双引号本身。)

我不确定您是否熟悉正则表达式,但对于任何编写 shell 脚本的人来说,它们都是非常宝贵的工具。例如,正则表达式/.o.e/将匹配任何第 2 个字母为小写字母o且第 4个字母为 的单词的行e。这将匹配包含诸如“ zone”、“ tone”甚至“ I am tone-deaf.”之类的单词的字符串

诀窍是使用.(点)字符来表示“任何字母”。还有一些其他特殊字符,例如*表示“将前一个字符重复 0 次或更多次”。因此,正则表达式 likea*将匹配“ a”、“ aaaaaaa”或空字符串:“”

因此,您可以使用以下方式匹配引号内的字符串:/".*"/

还有一件事你会知道sed(通过评论,你已经知道了!) - 它允许回溯。一旦你告诉它如何识别一个词,你就可以让它使用那个词作为替换的一部分。例如,假设您想翻转此列表:

Billy "The Kid" Smith
Jimmy "The Fish" Stuart
Chuck "The Man" Norris

进入这个列表:

The Kid
The Fish
The Man

首先,您将在引号内查找字符串。我们已经看到了,它是/".*"/

接下来,我们要使用引号内的内容。我们可以使用括号对其进行分组:/"(.*)"/

如果我们想用带下划线的引号替换文本,我们会做一个 replace: s/"(.*)"/_/,这将给我们留下:

Billy _ Smith
Jimmy _ Stuart
Chuck _ Norris

但我们有回溯!这将让我们回忆一下括号内的内容,使用符号\1. 因此,如果我们现在这样做:s/"(.*)"/\1/我们将得到:

Billy The Kid Smith
Jimmy The Fish Stuart
Chuck The Man Norris

因为引号不在括号中,所以它们不是\1!

为了只将内容留在双引号内,我们需要匹配整行。为此,我们有^(意思是“行首”)和$(意思是“行尾”。)

所以现在如果我们使用s/^.*"(.*)".*$/\1/,我们会得到:

The Kid
The Fish
The Man

为什么?s/^.*"(.*)".*$/\1/让我们从左到右阅读正则表达式:

  • s/- 开始一个替换正则表达式
  • ^- 寻找行首。从那里开始。
  • .*- 继续,阅读每个字符,直到...
  • "- ...直到你到达双引号。
  • (- 开始一组我们可能希望稍后回溯时回忆的角色。
  • .*- 继续,阅读每个字符,直到...
  • )- (pssst!关闭组!)
  • "- ...直到你到达双引号。
  • .*- 继续,阅读每个字符,直到...
  • $- 线的尽头!

  • /- 使用之后的内容来替换您匹配的内容

  • \1- 粘贴匹配的第一组(括号中的内容)的内容。
  • /- 正则表达式结束

用简单的英语:“阅读整行,复制双引号之间的文本。然后用双引号之间的内容替换整行。”

您甚至可以在替换文本周围添加双引号s/^.*"(.*)".*$/"\1"/,因此我们将得到:

"The Kid"
"The Fish"
"The Man"

这可以用于sed将行替换为引号中的内容:

sed -e "s/^.*\"\(.*\)\".*$/\"\1\"/"

(这只是外壳转义以处理双引号和斜杠等。)

所以整个命令将类似于:

cat named.local | grep zone | sed -e "s/^.*\"\(.*\)\".*$/\"\1\"/"
于 2009-04-27T07:18:12.603 回答
2

好吧,还没有人提到cut,所以,为了证明有很多方法可以用 shell 做某事:

% grep '^zone' /etc/bind/named.conf  | cut -d' ' -f2
"gennic.net"
"generic-nic.net"
"dyn.generic-nic.net"
"langtag.net"
于 2009-04-27T11:28:58.070 回答
1

1.

zoul@naima:etc$ cat named.conf | grep zone
zone "." IN {
zone "localhost" IN {
    file "localhost.zone";
zone "0.0.127.in-addr.arpa" IN {

2.

zoul@naima:etc$ cat named.conf | grep ^zone
zone "." IN {
zone "localhost" IN {
zone "0.0.127.in-addr.arpa" IN {

3.

zoul@naima:etc$ cat named.conf | grep ^zone | sed 's/.*"\([^"]*\)".*/\1/'
.
localhost
0.0.127.in-addr.arpa

正则表达式是.*"\([^"]*\)".*,它匹配:

  1. 任意数量的任意字符:.*
    • 报价:"
    • 以后开始记住:\(
    • 除引号外的任何字符:[^"]*
    • 结束组要记住:\)
    • 结束语:"
    • 和任意数量的字符:.*

调用sed时,语法为's/what_to_match/what_to_replace_it_with/'. 单引号可以防止您的正则表达式被bash. 当您使用括号“记住”正则表达式中的某些内容时,您可以将其召回为\1\2。摆弄一会儿。

于 2009-04-27T07:27:48.603 回答
0

你应该看看awk

于 2009-04-27T07:24:15.150 回答
0

只要有人指出 sed/awk,我就会指出 grep 是多余的。

sed -ne '/^zone/{s/.*"\([^"]*\)".*/\1/;p}' /etc/bind/named.conf

这为您提供了不带引号的内容(将引号移动到括号内以保留它们)。在 awk 中,使用引号更简单:

awk '/^zone/{print $2}' /etc/bind/named.conf

我尽量避免使用管道(但不是更多)。记住,不要管 cat。这不是必需的。而且,只要 awk 和 sed 复制 grep 的工作,也不要使用管道 grep。至少,不会变成 sed 或 awk。

就个人而言,我可能会使用 perl。但那是因为我可能已经完成了你在 perl 中所做的任何其他事情,使它成为一个小细节(并且能够同时将整个文件和正则表达式对所有内容进行 slurp,忽略 \n's 将是一个奖励我不控制 /etc/bind,例如在共享虚拟主机上)。但是,如果我要在 shell 中执行此操作,则上述两种方法中的一种将是我处理它的方式。

于 2009-04-27T15:41:01.710 回答