如何使用sed只输出捕获的组?

有没有一种方法告诉sed只输出捕获的组?

例如,给定输入:

This is a sample 123 text and some 987 numbers

和模式:

/([\d]+)/

我能得到只有123和987输出的方式格式化后引用?

553535 次浏览

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

有关示例和更多细节,请参见在这里

试一试

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
$

你可以使用grep

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

让它工作的关键是告诉sed排除你不想输出的内容,以及指定你想要输出的内容。这个技巧取决于你知道你要找多少匹配。下面的grep命令适用于未指定数量的匹配。

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/'

将输出“bar”。如果你使用-r (OS X使用-E)来扩展正则表达式,你不需要转义括号:

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

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

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

输出“条”。

如果你有GNU grep:

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

它也可以在BSD中工作,包括OS X:

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

这些命令将匹配任意数量的数字序列。输出将在多行上。

或者像这样的变化:

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

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

我相信问题中给出的模式只是举例,目标是匹配< em > < / em >模式。

如果你有一个带有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

这些例子是与tcsh(是的,我< em >知道< / em >它的错误shell)与CYGWIN。(编辑:对于bash,删除set和=周围的空格。)

这不是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

放弃吧,使用Perl

既然sed不能解决问题,让我们放弃并使用Perl,至少它是LSB,而grep GNU扩展不是:-)

  • 打印整个匹配部分,不需要匹配组或向后查找:

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

    输出:

    12
    3456
    
  • Single match per line, often structured data fields:

    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
    
  • Multiple fields:

    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
    
  • Multiple matches per line, often unstructured data:

    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
    

运行(s)位数

这个答案适用于任何计数的数字组。例子:

$ 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

和pattern: /([\d]+)/

Sed不识别'\d'(快捷键)语法。[0-9]上面使用的等价ascii并不完全等价。唯一的替代解决方案是使用字符类:'[[: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

这将包括重复的数字运行和编写一个短的(er)命令。

你可以使用ripgrep,它似乎也是简单替换的sed替换,就像这样

rg '(\d+)' -or '$1'

其中ripgrep使用-o--only matching-r--replace仅输出第一个捕获组,由于两次匹配,$1(引用以避免shell将其解释为变量)两次。

我想举一个关于“仅输出用sed"捕获的组”的简单例子。

我有/home/me/myfile-99,并希望输出文件的序列号:99

我的第一次尝试没有成功:

echo "/home/me/myfile-99" | sed -r 's/myfile-(.*)$/\1/'
# output: /home/me/99

为了做到这一点,我们还需要捕获捕获组中不需要的部分:

echo "/home/me/myfile-99" | sed -r 's/^(.*)myfile-(.*)$/\2/'
# output: 99

*)注意sed没有\d

您需要包含整行来打印组,这是您在第二个命令中所做的,但您不需要对第一个通配符进行分组。这也可以:

echo "/home/me/myfile-99" | sed -r 's/.*myfile-(.*)$/\1/'