让它工作的关键是告诉 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/'
输出“a bar a”。
如果您有 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 pcrepattern
或 man 3 pcresyntax
。
Sed 有多达九个记忆模式,但您需要使用转义括号来记忆正则表达式的部分内容。
有关示例和更多详细信息,请参见 here
sed -e 's/version=\(.+\)/\1/' input.txt
这仍然会输出整个 input.txt
\+
而不是 +
。而且我不明白为什么人们只将 -e
用于一个 sed 命令。
sed -e -n 's/version=\(.+\)/\1/p' input.txt
请参阅:mikeplate.com/2012/05/09/…
sed -E
来使用看起来更接近 Perl/Java/JavaScript/Go/任何风格的所谓“现代”或“扩展”正则表达式。 (比较 grep -E
或 egrep
。)默认语法有那些奇怪的转义规则,被认为是“过时的”。有关两者之间差异的更多信息,请运行 man 7 re_format
。
你可以使用 grep
grep -Eow "[0-9]+" file
o
选项的原因 - unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, --only-matching 仅显示匹配 PATTERN 的匹配行的一部分
grep -Eow -e "[0-9]+" -e "[abc]{2,3}"
我不知道你怎么能要求这两个表达式除了从前一个 grep 的管道之外在一行上(如果任一模式匹配不止一次,这仍然无法工作在一条线上)。
运行数字
这个答案适用于任何数量的数字组。例子:
$ 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'(快捷方式)语法。 [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)命令。
[^0-9]*([0-9]+)[^0-9]*
需要设计成永远不会越过另一个匹配的边界。这对于本示例来说是可行的,但对于不能逐个字符工作的复杂搜索查询,必须将其前向查找和反向查找否定。
放弃并使用 Perl
由于 sed
没有解决问题,让我们放弃并使用 Perl,至少它是 LSB 而 grep
GNU 扩展不是 :-)
打印整个匹配部分,不需要匹配组或后视:cat <
每行单个匹配,通常是结构化数据字段:cat <
多个字段:cat <
每行有多个匹配项,通常是非结构化数据:cat <
我相信问题中给出的模式仅作为示例,目标是匹配任何模式。
如果您有一个带有 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(是的,我知道它的 shell 错误)和 CYGWIN 一起使用。 (编辑:对于 bash,删除集合和 = 周围的空格。)
+
,您需要转义它或使用 -r
选项(OS X 为 -E
)。您也可以使用 \{1,\}
(或 -r
或 -E
没有转义)。
尝试
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
$
您需要在第二个命令中包含整行来打印组,但您不需要对第一个通配符进行分组。这也将起作用:
echo "/home/me/myfile-99" | sed -r 's/.*myfile-(.*)$/\1/'
这不是 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
我想举一个更简单的例子,关于“只用 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
您可以使用 ripgrep,它似乎也是简单替换的 sed 替代品,像这样
rg '(\d+)' -or '$1'
其中 ripgrep 使用 -o
或 --only matching
和 -r
或 --replace
仅输出第一个具有 $1
的捕获组(引用以避免被 shell 解释为变量)由于两次匹配而两次。
sed
示例,如果您使用-r
选项(或-E
对于 OS X,IIRC),则不需要转义括号。区别在于基本正则表达式和扩展正则表达式 (-r
) 之间的区别。\(.*\)
,使用\1
、\2
等访问捕获组..