如何获得模式的第一个和最后一次出现之间的所有线?


8

如何修剪文件(良好的输入流),以便仅获得从第一次出现foo到最后一次出现的行bar

例如,考虑以下输入:

A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest

我期望这个输出:

foo
this 
foo
bar
something
something else
foo
bar

3
单通流还是文件?当允许随机访问时,这样做更容易。使用文件,您只需找到第一个foo和最后一个,bar然后打印之间的所有内容(如果有)。对于流,您将必须读取直到first为止foo,并在内存中缓冲所有后续行,直到EOF,每次bar看到a时都要刷新缓冲区。这可能意味着将整个流缓冲在内存中。
jw013 2012年

Answers:


6
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//。我敢肯定还有更好的解决方案,但是不幸的是我没有想到。

希望一切都清楚。


1
有用!如果您能指导我们完成这样一个命令的构造,那将是非常酷的事情。我会觉得很愚蠢,只是从某些网站在线复制/粘贴;)
rahmu 2012年

1
抱歉,我没有提供解释的答案。现在就在帖子中。

在某些sed版本中,例如BSD sed(在Mac上可以找到),标记需要后接换行符或字符串结尾,因此需要进行以下调整: sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};'适用于GNU sed,因此我认为这种修改(多个-eargs在sed中使用分支时,要养成一种很好的可移植性习惯,即在每个分支名称后加一个arg)。
2015年

4

我会用一点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

3
如果这是代码高尔夫,则可以使用E代替e-00777代替该$/位(请参见perlrun(1))。它将缩短为:perl -0777 -nE 'say /(foo.*bar)/s',仍然可读。
雷神

1
我不知道这些标志!我相信特别是-0[octal]在我的工作流程中会找到它的方式!谢谢你
user1146332 2012年

3

这是不需要大量内存的两遍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,不带括号。
  • 最后一次调用sedpass,infile再次打印找到的地址及其之间的所有内容。

2

如果输入文件适合存储在内存中,请使其简单

如果输入文件是巨大的,你可以用csplit它闯入的第一件foo,并在每个随后bar再组装件。这些片段称为piece-000000000piece-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

1

这是另一种方式sed

sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile

它将/foo/,$范围内的每行(删除!不在此范围内的行)附加dH旧空间。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'

0

在任何UNIX系统上的任何外壳中使用任何awk,而无需一次将整个文件或输入流读取到内存中:

$ awk '
    f {
        rec = rec $0 ORS
        if (/bar/) {
            printf "%s", rec
            rec = ""
        }
        next
    }
    /foo/ { f=1; rec=$0 ORS }
' file
foo
this
foo
bar
something
something else
foo
bar

0

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.