我有一个问题,我想解析文件的输出,我想在两个模式之间获取第 n 次出现的文本,最好使用 awk 或 sed
category
1
s
t
done
category
2
n
d
done
category
3
r
d
done
category
4
t
h
done
假设对于这个例子,我想在 category 和 done 之间获取第三次出现的文本,基本上输出是
category
3
r
d
done
这可能对您有用(GNU sed):
'sed -n '/category/{:a;N;/done/!ba;x;s/^/x/;/^x\{3\}$/{x;p;q};x}' file
-n
使用该选项关闭自动打印。收集 和 之间的category
线done
。在保持空间中存储一个计数器,当它达到 3 时,在模式空间中打印集合并退出。
或者,如果您更喜欢 awk:
awk '/^category/,/^done/{if(++m==1)n++;if(n==3)print;if(/^done/)m=0}' file
尝试这样做:
awk -v n=3 '/^category/{l++} (l==n){print}' file.txt
或者更神秘:
awk -v n=3 '/^category/{l++} l==n' file.txt
如果您的文件很大:
awk -v n=3 '/^category/{l++} l>n{exit} l==n' file.txt
如果您的文件不包含任何空字符,则正在使用GNU sed
. 这将找到模式范围的第三次出现。但是,您可以轻松地对其进行修改以获得您想要的任何事件。
sed -n '/^category/ { x; s/^/\x0/; /^\x0\{3\}$/ { x; :a; p; /done/q; n; ba }; x }' file.txt
结果:
category
3
r
d
done
解释:
使用开关关闭默认打印-n
。在行首匹配单词“category”。将模式空间与保持空间交换,并将空字符附加到模式的开头。在示例中,如果模式随后包含两个前导空字符,则将模式拉出保持空间。现在创建一个循环并打印模式空间的内容,直到匹配最后一个模式。当找到最后一个模式时,sed
将退出。如果没有找到sed
,将继续读取下一行输入并继续循环。
awk -v tgt=3 '
/^category$/ { fnd=1; rec="" }
fnd {
rec = rec $0 ORS
if (/^done$/) {
if (++cnt == tgt) {
printf "%s",rec
exit
}
fnd = 0
}
}
' file
使用 GNU awk,您可以将记录分隔符设置为正则表达式:
<file awk 'NR==n+1 { print rt, $0 } { rt = RT }' RS='\\<category' ORS='' n=3
输出:
category
3
r
d
done
RT
是匹配的记录分隔符。请注意,相对于 的记录n
将关闭一个,因为第一条记录指的是第一条之前的记录RS
。
根据 Ed 的评论,当记录之间有其他数据时,这将不起作用,例如:
category
1
s
t
done
category
2
n
d
done
foo
category
3
r
d
done
bar
category
4
t
h
done
解决此问题的一种方法是使用第二个(或第一个)awk 清理输入:
<file awk '/^category$/,/^done$/' |
awk 'NR==n+1 { print rt, $0 } { rt = RT }' RS='\\<category' ORS='' n=3
输出:
category
3
r
d
done
正如Ed在评论中指出的那样,上述方法不搜索结束模式。其他答案未涵盖的一种方法是使用(请注意,awk getlinegetline
有一些警告):
<file awk '
/^category$/ {
v = $0
while(!/^done$/) {
if(!getline)
exit
v = v ORS $0
}
if(++nr == n)
print v
}' n=3
一行:
<file awk '/^category$/ { v = $0; while(!/^done$/) { if(!getline) exit; v = v ORS $0 } if(++nr == n) print v }' n=3