如何仅输出带有sed的捕获组?


277

有什么办法告诉sed只输出捕获的组吗?例如给出输入:

This is a sample 123 text and some 987 numbers

和模式:

/([\d]+)/

我只能以反向引用的格式获得123和987输出吗?


注意,组捕获需要sed使用该-E标志打开扩展的正则表达式。
彼得-恢复莫妮卡

Answers:


333

使此功能生效的关键是告诉sed您排除不希望输出的内容,并指定所需的内容。

string='This is a sample 123 text and some 987 numbers'
echo "$string" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

这说:

  • 不要默认打印每行(-n
  • 排除零个或多个非数字
  • 包含一个或多个数字
  • 排除一个或多个非数字
  • 包含一个或多个数字
  • 排除零个或多个非数字
  • 打印替换(p

通常,在sed使用括号捕获组中并使用向后引用输出捕获的内容:

echo "foobarbaz" | sed 's/^foo\(.*\)baz$/\1/'

将输出“栏”。如果您将-r-E用于OS X)用于扩展正则表达式,则无需转义括号:

echo "foobarbaz" | sed -r 's/^foo(.*)baz$/\1/'

最多可以有9个捕获组及其反向引用。后面的引用按组出现的顺序编号,但是它们可以按任何顺序使用并可以重复:

echo "foobarbaz" | sed -r 's/^foo(.*)b(.)z$/\2 \1 \2/'

输出“ bar a”。

如果您具有GNU grep(它也可能在BSD(包括OS X)中运行):

echo "$string" | grep -Po '\d+'

或诸如以下的变体:

echo "$string" | grep -Po '(?<=\D )(\d+)'

-P选项启用Perl兼容的正则表达式。请参阅man 3 pcrepatternman 3 pcresyntax


24
注意,OSX Mountain Lion不再在grep中支持PCRE。
yincrash 2012年

1
附带说明,Solaris 9不支持grep -o选项。此外,Solaris 9不支持sed -r选项。:(
丹尼尔·卡茨

7
请您的系统管理员安装gsed。您会惊讶于一些甜甜圈会给您带来什么……
avgvstvs 2012年

3
请注意,您可能需要在'('和')'前面加上'\',但我不知道为什么。
lumbric

7
@lumbric:如果您引用的是sed示例,则使用-r选项(或-E对于OS X,是IIRC),则不需要转义括号。基本正则表达式和扩展正则表达式(-r)之间的区别。
暂停,直到另行通知。

55

Sed最多可以记住9种模式,但是您需要使用转义括号来记住正则表达式的各个部分。

看到这里的例子和更多细节


58
sed -e 's/version=\(.+\)/\1/' input.txt这仍然会输出整个input.txt
Pablo 2010年

@Pablo,在您的模式中,您必须编写\+而不是+。而且我不明白为什么人们只使用-e一个sed命令。
弗雷德里克·高斯

1
使用sed -e -n 's/version=\(.+\)/\1/p' input.txt请参阅:mikeplate.com/2012/05/09/…–
awattar

1
我建议使用sed -E所谓的“现代”或“扩展”正则表达式,它们看起来更接近Perl / Java / JavaScript / Go /任何形式。(与grep -E或相比egrep。)默认语法具有那些奇怪的转义规则,被认为是“过时的”。有关两者之间差异的更多信息,请运行man 7 re_format
AndrewF

31

你可以使用grep

grep -Eow "[0-9]+" file

4
@ ghostdog74:完全同意你的看法。如何获得greo仅输出捕获的组?
巴勃罗

1
@迈克尔-这就是为什么o选择是有- unixhelp.ed.ac.uk/CGI/man-cgi?grep:-o,--only匹配显示匹配行的只有一部分匹配pattern
伯特˚F

14
@伯特·F:我了解匹配的部分,但不是捕获组。我想要的是这样([0-9] +)。+([abc] {2,3}),所以有2个捕获组。我只想输出通过反向引用或其他方式捕获的组。
巴勃罗

你好迈克尔。您是否通过grep提取了第n个捕获的组?
doc_id 2011年

1
@Pablo:grep仅输出匹配项。要给它提供多个组,请使用多个表达式:grep -Eow -e "[0-9]+" -e "[abc]{2,3}"我不知道您如何可以要求将这两个表达式除了从上一个grep进行管道传递之外,必须位于一行上(如果任一模式在一行上匹配多个,则仍然无法使用)。
idbrii 2012年

13

几位数

此答案适用于任何数量的数字组。例:

$ echo 'Num123that456are7899900contained0018166intext' |
> sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

扩展答案。

有什么办法告诉sed仅输出捕获的组?

是。用捕获组替换所有文本:

$ echo 'Number 123 inside text' | sed 's/[^0-9]*\([0-9]\{1,\}\)[^0-9]*/\1/'
123

s/[^0-9]*                           # several non-digits
         \([0-9]\{1,\}\)            # followed by one or more digits
                        [^0-9]*     # and followed by more non-digits.
                               /\1/ # gets replaced only by the digits.

或使用扩展语法(较少的反引号,并允许使用+):

$ echo 'Number 123 in text' | sed -E 's/[^0-9]*([0-9]+)[^0-9]*/\1/'
123

为避免在没有数字时打印原始文本,请使用:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n)默认不打印输入。
  • (/ p)仅在完成更换后打印。

并匹配多个数字(并打印它们):

$ echo 'N 123 in 456 text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1 /gp'
123 456

这适用于任何位数的运行:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

这与grep命令非常相似:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | grep -Po '\d+'
123
456
7899900
0018166

关于\ d

和模式: /([\d]+)/

Sed无法识别'\ d'(快捷方式)语法。上面使用的ascii等效项[0-9]并不完全等效。唯一的替代解决方案是使用字符类:'[[:digit:]]`。

选择的答案使用此类“字符类”构建解决方案:

$ str='This is a sample 123 text and some 987 numbers'
$ echo "$str" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

该解决方案仅适用于(完全)两个数字位数。

当然,由于答案是在Shell中执行的,因此我们可以定义几个变量以使答案更短:

$ str='This is a sample 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D+($d+)$D*/\1 \2/p"

但是,正如已经说明的那样,使用s/…/…/gp命令更好:

$ str='This is 75577 a sam33ple 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D*/\1 /gp"
75577 33 123 987

这将覆盖数字的重复运行和编写短命令。


在阅读了高票赞成的答案后,我感到很惊讶,我向下滚动以写出其范围狭窄并真正解决问题的精神。我应该猜到有人会在几年前做到这一点。这很好地解释了,是真正的正确答案。
阿米特·奈杜

9

我相信问题中给出的模式仅是示例,目标是匹配任何模式。

如果您使用的是带有GNU扩展名的sed,允许在模式空间中插入换行符,则建议:

> set string = "This is a sample 123 text and some 987 numbers"
>
> set pattern = "[0-9][0-9]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
123
987
> set pattern = "[a-z][a-z]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
his
is
a
sample
text
and
some
numbers

这些示例与CYGWIN 一起用于tcsh(是的,我知道它的外壳错误)。(编辑:对于bash,删除set和=周围的空格。)


@Joseph:谢谢,但是根据我的任务,我觉得grep更自然,就像ghostdog74建议的那样。只需要弄清楚如何使grep输出仅捕获组,而不是整个匹配组即可。
巴勃罗

2
只是一个注释,但加号“ +”表示“一个或多个”,这将消除在模式中重复自己的需要。因此,“ [0-9] [0-9] *”将变为“ [0-9] +”
RandomInsano 2012年

4
@RandomInsano:要使用+,您需要对其进行转义或使用-r选项(-E对于OS X)。您也可以使用\{1,\}-r-E不使用转义符)。
暂停,直到另行通知。

9

放弃并使用Perl

由于sed不切实际,我们就扔毛巾使用Perl,至少它是LSB,grepGNU扩展不是:-)

  • 打印整个匹配部分,无需匹配组或后退:

    cat <<EOS | perl -lane 'print m/\d+/g'
    a1 b2
    a34 b56
    EOS

    输出:

    12
    3456
  • 每行一次匹配,通常是结构化的数据字段:

    cat <<EOS | perl -lape 's/.*?a(\d+).*/$1/g'
    a1 b2
    a34 b56
    EOS

    输出:

    1
    34

    往后看:

    cat <<EOS | perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
  • 多个字段:

    cat <<EOS | perl -lape 's/.*?a(\d+).*?b(\d+).*/$1 $2/g'
    a1 c0 b2 c0
    a34 c0 b56 c0
    EOS

    输出:

    1 2
    34 56
  • 每行多次匹配,通常是非结构化数据:

    cat <<EOS | perl -lape 's/.*?a(\d+)|.*/$1 /g'
    a1 b2
    a34 b56 a78 b90
    EOS

    输出:

    1 
    34 78

    往后看:

    cat EOS<< | perl -lane 'print m/(?<=a)(\d+)/g'
    a1 b2
    a34 b56 a78 b90
    EOS

    输出:

    1
    3478

1
问题的结尾没有得到什么:“ with sed”?
Moonchild's

@Moonchild Googlers不在乎。
西罗Santilli郝海东冠状病六四事件法轮功

1
我发现这很有用。并非所有命令行正则表达式问题都需要使用sed解决。
–PPPaul

5

尝试

sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

我在cygwin下得到了这个:

$ (echo "asdf"; \
   echo "1234"; \
   echo "asdf1234adsf1234asdf"; \
   echo "1m2m3m4m5m6m7m8m9m0m1m2m3m4m5m6m7m8m9") | \
  sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

1234
1234 1234
1 2 3 4 5 6 7 8 9
$

2

这不是OP要求的(捕获组),但是您可以使用以下方法提取数字:

S='This is a sample 123 text and some 987 numbers'
echo "$S" | sed 's/ /\n/g' | sed -r '/([0-9]+)/ !d'

给出以下内容:

123
987
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.