在为问题How to extract content between two patterns in Unix开发答案时,我在sed
中遇到了一个我无法解释的行为 - 你可以吗?
数据文件:data
Goodbye
select *
from dep
where jkdsfj
select *
from sal
where jkdsfj
select elephants
from abject poverty
join flying tigers
where abelone = shellfish;
select mouse
from toolset
join animals where tail = cord
and buttons = legs
Hello
目标是选择单词from
和where
之间的文字。
以下是脚本的4种变体:
script.16
/from/,/where/ { s/.*from *//; s/ *where.*//; /^ *$/d; p; }
script.17
# Bust by final n;
/from/,/where/ { s/.*from *//; s/ *where.*//; /^ *$/d; p; n; }
script.18
/from/,/where/ { s/.*from *//; s/ *where.*//; /^ *$/d; p; d; }
script.19
/from/,/where/ { s/.*from *//; s/ *where.*//; /^ *$/d; p; b
}
这些都适用于BSD(Mac OS X)sed
和GNU sed
。最后一个脚本可以使用b; }
,它可以与GNU sed
一起使用,但BSD sed
拒绝它。
麻烦的是script.17
的输出与其他3的输出不同,我无法理解为什么:
$ sed -n -f script.16 data
dep
sal
abject poverty
join flying tigers
toolset
join animals
$ sed -n -f script.17 data
dep
select *
abject poverty
toolset
and buttons = legs
Hello
$
为什么输出中有select *
和and buttons = legs
以及Hello
?
$ sed -n -f script.18 data
dep
sal
abject poverty
join flying tigers
toolset
join animals
$ sed -n -f script.19 data
dep
sal
abject poverty
join flying tigers
toolset
join animals
$
为什么使用n
会改变sed
这样的行为?从我尝试过诊断'打印的某些变体中可以看出,n
阻止sed
识别where
何时正确显示,但b
和d
都跳转到下一个周期,而不是像n
那样正常,但有些不同。
鉴于两个独立的实现做同样的事情,我不得不假设它是有意的,但是......为什么?
答案 0 :(得分:4)
问题在于范围以及评估范围时图案空间中的内容。
在评估范围时,sed中的范围端点与模式空间的内容匹配,而不是与原始输入线相关。因此,对于sed -n '/start/,/end/{...}'
,重要的是命令开头的模式空间中的内容,而不是命令处理后的模式空间中的内容,或n
导致更多的行读取。
p;n
与范围相结合的问题可以用更简单的代码来说明。请注意,与b
和d
不同,命令n
读入一行。因此,sed -n 'p;n'
每隔一行打印一次。例如:
$ seq 5 | sed -n 'p;n'
1
3
5
现在,结合范围观察p;n
:
$ seq 5 | sed -n '/1/,/3/{p;n;}'
1
3
以上工作符合预期。然而,以下内容令人惊讶:
$ seq 5 | sed -n '/1/,/2/{p;n;}'
1
3
5
包含2
的行由n
命令读入,然后立即被丢弃。评估范围2
时,包含/1/,/2/
的行不会出现在模式空间中。因此,sed
永远不会看到/1/,/2/
的结尾,并且一直认为它在范围内。
现在,让我们考虑你的脚本17,稍加修改:
sed -n '/from/,/where/ { s/.*from */BEGIN/; s/ *where.*/END/; /^ *$/d; p; n; }' data
BEGINdep
select *
END
BEGINabject poverty
END
BEGINtoolset
and buttons = legs
Hello
在这里,我们看到范围/from/,/where/
从from
的外观继续到下一次where
出现在命令开头的模式缓冲区中时评估范围。由where
读取的n
实例永远不会结束范围。
考虑文件中出现/1/,/END/
nevers的范围END
:
$ seq 5 | sed -n 's/3/END/; /1/,/END/{p;n}'
1
END
即使END
nevers出现在文件中,它也会在评估范围时出现在模式空间中。因此,它结束了范围。
再一次演示,让我们改变上述命令的顺序。下面,我们看到END
虽然打印出来但并未结束范围:
$ seq 5 | sed -n ' /1/,/END/{s/3/END/; p; n}'
1
END
5
这是因为评估范围时END
不在模式空间中。因此,sed
永远不会看到范围的结束。