【发布时间】:2013-10-22 01:49:04
【问题描述】:
我想要一个 unix 命令来查找单词的第一次和最后一次出现之间的行
例如:
假设我们有 1000 行。第十行包含单词“stackoverflow”,第三十五行还包含单词“stackoverflow”。
我想打印 10 到 35 之间的行并将其写入新文件。
【问题讨论】:
-
与其想象,为什么不创建一个示例输入文件和预期的输出供我们处理?
我想要一个 unix 命令来查找单词的第一次和最后一次出现之间的行
例如:
假设我们有 1000 行。第十行包含单词“stackoverflow”,第三十五行还包含单词“stackoverflow”。
我想打印 10 到 35 之间的行并将其写入新文件。
【问题讨论】:
您可以分两步完成。基本思路是:
1) 获取第一个和最后一个匹配的行号。
2) 打印这些范围之间的行范围。
$ read first last <<< $(grep -n stackoverflow your_file | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}')
$ awk -v f=$first -v l=$last 'NR>=f && NR<=l' your_file
read first last 读取两个值并将它们存储在 $first 和 $last 中。grep -n stackoverflow your_file greps 并显示如下输出:number_of_line:output
awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}') 打印文件中stackoverflow 的第一个和最后一个匹配的行号。和
awk -v f=$first -v l=$last 'NR>=f && NR<=l' your_file 打印从$first 行号到$last 行号的所有行。$ cat a
here we
have some text
stackoverflow
and other things
bla
bla
bla bla
stackoverflow
and whatever else
stackoverflow
to make more fun
blablabla
$ read first last <<< $(grep -n stackoverflow a | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}')
$ awk -v f=$first -v l=$last 'NR>=f && NR<=l' a
stackoverflow
and other things
bla
bla
bla bla
stackoverflow
and whatever else
stackoverflow
按步骤:
$ grep -n stackoverflow a
3:stackoverflow
9:stackoverflow
11:stackoverflow
$ grep -n stackoverflow a | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}'
3 11
$ read first last <<< $(grep -n stackoverflow a | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}')
$ echo "first=$first, last=$last"
first=3, last=11
【讨论】:
如果您知道可以有多少行的上限(比如一百万行),那么您可以使用这个简单的滥用脚本:
(grep -A 100000 stackoverflow | grep -B 1000000 stackoverflow) < file
您也可以附加| tail -n +2 | head -n -1 来去除边框线:
(grep -A 100000 stackoverflow | grep -B 1000000 stackoverflow
| tail -n +2 | head -n -1) < file
【讨论】:
对于输出是否应该包含第一行和最后一个匹配行的问题,我不能 100% 确定,所以我假设它是。但是如果我们想要独占,这可以很容易地改变。
这个纯 bash 解决方案一步完成 - 即文件(或管道)只读取一次:
#!/bin/bash
function midgrep {
while read ln; do
[ "$saveline" ] && linea[$((i++))]=$ln
if [[ $ln =~ $1 ]]; then
if [ "$saveline" ]; then
for ((j=0; j<i; j++)); do echo ${linea[$j]}; done
i=0
else
saveline=1
linea[$((i++))]=$ln
fi
fi
done
}
midgrep "$1"
将其保存为脚本(例如 midgrep.sh)并将您喜欢的任何输出通过管道传递给它,如下所示:
$ cat input.txt | ./midgrep.sh stackoverflow
它的工作原理如下:
这种方法的优点是我们只读取一次输入。缺点是我们在每次匹配之间缓冲所有内容 - 如果每次匹配之间有很多行,那么这些都缓冲到内存中,直到我们遇到下一个匹配。
这也使用了 bash =~ 正则表达式运算符来保持这个纯 bash。但是,如果您对此更满意,则可以将其替换为 grep。
【讨论】:
使用perl:
perl -00 -lne '
chomp(my @arr = split /stackoverflow/);
print join "\nstackoverflow", @arr[1 .. $#arr -1 ]
' file.txt | tee newfile.txt
这背后的想法是使用“stackoverflow”字符串将整个输入文件的数组输入到块中以进行拆分。接下来,我们使用 join "stackoverflow" 将第 2 个匹配项打印到最后一个 -1。
【讨论】: