Answers:
sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};'
sed模式匹配/first/,/second/
一一读取行。当某些行/first/
与之匹配时会记住它,并期待该/second/
模式的第一个匹配项。同时,它将应用为该模式指定的所有活动。之后,该过程将一次又一次地开始直到文件结尾。
那不是我们所需要的。我们需要查找/second/
模式的最后一个匹配项。因此,我们构建的建筑只寻找第一个入口/foo/
。找到后,循环a
开始。我们使用将新行添加到匹配缓冲区,N
并检查它是否与模式匹配/bar/
。如果是这样,我们只打印它并清除匹配缓冲区,然后用跳转到循环的开始ba
。
同样,在用清除缓冲区后,我们需要删除换行符/^\n/s/^\n//
。我敢肯定还有更好的解决方案,但是不幸的是我没有想到。
希望一切都清楚。
sed
版本中,例如BSD sed(在Mac上可以找到),标记需要后接换行符或字符串结尾,因此需要进行以下调整: sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};'
这也适用于GNU sed,因此我认为这种修改(多个-e
args在sed中使用分支时,要养成一种很好的可移植性习惯,即在每个分支名称后加一个arg)。
我会用一点Perl单线来做到这一点。
cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)'
A line
like
foo
this
foo
bar
something
something else
foo
bar
and
the
rest
EOF
产量
foo
this
foo
bar
something
something else
foo
bar
E
代替e
和-00777
代替该$/
位(请参见perlrun(1))。它将缩短为:perl -0777 -nE 'say /(foo.*bar)/s'
,仍然可读。
-0[octal]
在我的工作流程中会找到它的方式!谢谢你
这是不需要大量内存的两遍GNU sed解决方案:
< infile \
| sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \
| sed -n '1p; $p' \
| tr '\n' ' ' \
| sed 's/ /,/; s/ /p/' \
| sed -n -f - infile
sed
调用传递infile并找到的第一个匹配项foo
和所有随后的匹配项bar
。sed
脚本,其中包含两次调用sed
和一个tr
。第三个的输出sed
是[start_address],[end_address]p
,不带括号。sed
pass,infile
再次打印找到的地址及其之间的所有内容。如果输入文件适合存储在内存中,请使其简单。
如果输入文件是巨大的,你可以用csplit
它闯入的第一件foo
,并在每个随后bar
再组装件。这些片段称为piece-000000000
,piece-000000001
等等。选择一个piece-
不会与其他现有文件冲突的前缀(此处为)。
csplit -f piece- -n 9 - '%foo%' '/bar/' '{*}' <input-file
(在非Linux系统上,您必须在花括号内使用大量数字,例如{999999999}
,并传递该-k
选项。该数字是段数bar
。)
您可以用组装所有零件cat piece-*
,但这将为您提供一切后的一切foo
。因此,请先删除最后一块。由于产生的文件名csplit
不包含任何特殊字符,因此可以在不采取任何特殊引号预防措施的情况下使用它们,例如
rm $(echo piece-* | sed 's/.* //')
或同等
rm $(ls piece-* | tail -n 1)
现在,您可以加入所有片段并删除临时文件:
cat piece-* >output
rm piece-*
如果要删除连接起来的碎片以节省磁盘空间,请循环执行:
mv piece-000000000 output
for x in piece-?????????; do
cat "$x" >>output; rm "$x"
done
这是另一种方式sed
:
sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile
它将/foo/,$
范围内的每行(删除!
不在此范围内的行)附加d
到H
旧空间。bar
然后删除不匹配的行。在匹配的行上,将清空模式空间,x
用保留空间进行更改,并删除模式空间中的前导空行。
在输入大量信息的情况下,bar
与将每一行拖入模式空间然后每次检查模式空间中的相比,这种情况发生的速度(快得多)要快得多bar
。
解释:
sed '/foo/,$!d # delete line if not in this range
H # append to hold space
/bar/!d # if it doesn't match bar, delete
s/.*// # otherwise empty pattern space and
x # exchange hold buffer w. pattern space then
s/\n// # remove the leading newline
' infile
当然,如果这是一个文件(并且适合内存),则可以简单地运行:
ed -s infile<<'IN'
.t.
/foo/,?bar?p
q
IN
因为ed
可以向前和向后搜索。
如果您的外壳支持进程替换,您甚至可以将命令输出读入文本缓冲区:
printf '%s\n' .t. /foo/,?bar?p q | ed -s <(your command)
或者,如果没有,则使用gnu ed
:
printf '%s\n' .t. /foo/,?bar?p q | ed -s '!your command'
Grep也可以做到这一点(GNU grep):
<infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
<infile grep -ozP ' # call grep to print only the matching section (`-o`)
# use NUL for delimiter (`-z`) (read the whole file).
# And using pcre regex.
(?s)foo.*bar # Allow the dot (`.`) to also match newlines.
' | tr '\0' '\n' # Restore the NULs to newlines.
对于问题正文的输入:
$ <infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
foo
this
foo
bar
something
something else
foo
bar
foo
和最后一个,bar
然后打印之间的所有内容(如果有)。对于流,您将必须读取直到first为止foo
,并在内存中缓冲所有后续行,直到EOF,每次bar
看到a时都要刷新缓冲区。这可能意味着将整个流缓冲在内存中。