如何使用sed,awk或gawk仅打印匹配的内容?


100

我看到许多示例和手册页,介绍如何使用sed,awk或gawk进行搜索和替换。

但就我而言,我有一个正则表达式,我想对文本文件运行以提取特定值。我不想进行搜索和替换。这是从bash调用的。我们来看一个例子:

正则表达式示例:

.*abc([0-9]+)xyz.*

输入文件示例:

a
b
c
abc12345xyz
a
b
c

听起来很简单,但我不知道如何正确调用sed / awk / gawk。我希望做的是从我的bash脚本中获得:

myvalue=$( sed <...something...> input.txt )

我尝试过的事情包括:

sed -e 's/.*([0-9]).*/\\1/g' example.txt # extracts the entire input file
sed -n 's/.*([0-9]).*/\\1/g' example.txt # extracts nothing

10
哇...人们把这个问题投下-1?真的有问题吗?
斯特凡2009年

使用Regex和功能强大的命令行实用程序(如sed / awk)或任何编辑器(如vi,emacs或teco)似乎更像是编程,而不仅仅是使用某些ol'应用程序。IMO比SO更属于SO。
09年

也许它被否决了,因为它在最初的形式中并未明确定义一些要求。除非您阅读了OP对答案的评论(包括我在事情发展成梨形时删除的评论),否则它仍然没有。
pavium

Answers:


42

我的sed(Mac OS X)无法使用+。我*改为尝试,并添加了p用于打印匹配项的标签:

sed -n 's/^.*abc\([0-9]*\)xyz.*$/\1/p' example.txt

用于匹配至少一个数字字符而没有 +,我将使用:

sed -n 's/^.*abc\([0-9][0-9]*\)xyz.*$/\1/p' example.txt

谢谢,这在我使用*代替+后也对我有用。
斯特凡(Stéphane)

2
...和“ p”选项以打印匹配项,我都不知道。再次感谢。
斯特凡2009年

2
我不得不逃脱了+,然后对我sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
有用

3
那是因为您没有使用现代的RE格式,所以+是标准字符,应该使用{,}语法来表示。您可以添加use -E sed选项来触发现代RE格式。检查re_format(7),特别是
Description

33

您可以使用sed执行此操作

 sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
  • -n 不要打印结果行
  • -r 这样就可以避免捕获组逃避()
  • \1 捕获组匹配
  • /g 全球比赛
  • /p 打印结果

我为自己编写了一个工具,可以简化此工作

rip 'abc(\d+)xyz' '$1'

3
到目前为止,这是最好的,也是最能解释的答案!
Nik

通过一些解释,更好地理解我们的问题出在哪里。谢谢 !
r4phG

17

perl过去常常为自己做这件事。例如

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/'

这将运行Perl,该-n选项指示Perl一次从STDIN中读取一行并执行代码。该-e选项指定要运行的指令。

该指令在读取的行上运行一个正则表达式,如果匹配则打印出第一组括号($1)的内容。

您可以执行此操作,还将在末尾添加多个文件名。例如

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/' example1.txt example2.txt


谢谢,但是我们无法访问perl,这就是为什么我要询问sed / awk / gawk。
斯特凡(Stéphane)

5

如果您的grep支持版本,则可以使用该-o选项打印与正则表达式匹配的任何行的一部分。

如果没有,那么这就是sed我能想到的最好的方法:

sed -e '/[0-9]/!d' -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

...删除/跳过没有数字的字符,而对于其余的行,则删除所有前导和尾随的非数字字符。(我只是猜测您的意图是从包含一个的每一行中提取数字)。

问题类似于:

sed -e 's/.*\([0-9]*\).*/&/' 

.... 要么

sed -e 's/.*\([0-9]*\).*/\1/'

... sed仅支持“贪婪”匹配。因此第一个。*将与该行的其余部分匹配。除非我们可以使用否定的字符类来实现非贪婪匹配...或sed具有Perl兼容版本或正则表达式的其他扩展,否则我们无法从模式空间(行)。


您可以sed通过这种方式组合两个命令:sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p'
已暂停,直到另行通知。

以前不了解grep上的-o选项。很高兴知道。但是它会打印整个匹配项,而不是“(...)”。因此,如果您匹配“ abc([[:digit:]] +)xyz”,那么您将获得“ abc”和“ xyz”以及数字。
斯特凡

感谢您让我想起grep -o!我试图做到这一点,sed并且为在某些行上找到多个匹配项而苦恼。我的解决方案是stackoverflow.com/a/58308239/117471
Bruno Bronosky '19年

3

您可以使用awkwith match()访问捕获的组:

$ awk 'match($0, /abc([0-9]+)xyz/, matches) {print matches[1]}' file
12345

这将尝试匹配模式abc[0-9]+xyz。如果这样做,它将切片存储在数组中matches,数组的第一项是block [0-9]+。由于match() 返回子字符串开始的字符位置或索引(如果从字符串的开头开始,则为1),因此它将触发print操作。


grep您一起可以使用前瞻性和前瞻性:

$ grep -oP '(?<=abc)[0-9]+(?=xyz)' file
12345

$ grep -oP 'abc\K[0-9]+(?=xyz)' file
12345

[0-9]+当它出现在abc并检查xyz数字时,将检查该模式。


2

perl是最干净的语法,但是如果您没有perl(据我了解,并不总是存在),那么使用gawk和正则表达式组件的唯一方法就是使用gensub功能。

gawk '/abc[0-9]+xyz/ { print gensub(/.*([0-9]+).*/,"\\1","g"); }' < file

样本输入文件的输出将是

12345

注意:gensub替换了整个正则表达式(位于//之间),因此您需要在([0-9] +)之前和之后放置。*,以消除替换中的数字之前和之后的文本。


2
如果您需要(或想要)使用gawk,那么这是一个聪明,可行的解决方案。您注意到了这一点,但要清楚一点:非GNU awk没有gensub(),因此不支持此功能。
cincodenada 2014年

真好!但是,最好使用match()访问捕获的组。看到我的答案
fedorqui'SO停止伤害

1

如果要选择行,则去除不需要的位:

egrep 'abc[0-9]+xyz' inputFile | sed -e 's/^.*abc//' -e 's/xyz.*$//'

它基本上选择了您要使用的行egrep,然后用于sed剥离数字前后的位。

您可以在这里看到实际效果:

pax> echo 'a
b
c
abc12345xyz
a
b
c' | egrep 'abc[0-9]+xyz' | sed -e 's/^.*abc//' -e 's/xyz.*$//'
12345
pax> 

更新:很明显,如果您的实际情况更加复杂,那么我需要对RE进行修改。例如,如果您始终在一个数字的开头和结尾处都包含一个零或多个非数字的数字:

egrep '[^0-9]*[0-9]+[^0-9]*$' inputFile | sed -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

有趣的是...因此,没有一种简单的方法来应用复杂的正则表达式并获得(...)部分中的内容?因为虽然我看到您首先使用grep然后使用sed在这里做了什么,但我们的实际情况比删除“ abc”和“ xyz”要复杂得多。使用正则表达式是因为很多不同的文本可以出现在我要提取的文本的两侧。
斯特凡

我敢肯定有一个更好的方式,如果RE比较非常复杂。也许如果您提供了更多示例或更详细的说明,我们可以调整答案以适合您的情况。
paxdiablo

0

OP的案例并未指定一行上可以有多个匹配项,但是对于Google流量,我也将为此添加一个示例。

由于OP的需要是从图案中提取组,因此使用grep -o将需要2次通过。但是,我仍然发现这是完成工作的最直观的方法。

$ cat > example.txt <<TXT
a
b
c
abc12345xyz
a
abc23451xyz asdf abc34512xyz
c
TXT

$ cat example.txt | grep -oE 'abc([0-9]+)xyz'
abc12345xyz
abc23451xyz
abc34512xyz

$ cat example.txt | grep -oE 'abc([0-9]+)xyz' | grep -oE '[0-9]+'
12345
23451
34512

由于处理器时间基本上是免费的,但是人类可读性却是无价的,所以我倾向于基于“一年以后,我打算怎么做”这个问题来重构代码。实际上,对于我打算公开或与我的团队共享的代码,我什man grep至会公开弄清长选项是什么,然后替代它们。像这样:grep --only-matching --extended-regexp


-1

你可以用壳做

while read -r line
do
    case "$line" in
        *abc*[0-9]*xyz* ) 
            t="${line##abc}"
            echo "num is ${t%%xyz}";;
    esac
done <"file"

-3

对于awk。我将使用以下脚本:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }

这不会输出数值([0-9+]),而是输出整行。
马克·拉卡塔

-3
gawk '/.*abc([0-9]+)xyz.*/' file

2
这似乎不起作用。它打印整个行而不是匹配项。
斯特凡2009年

在示例输入文件中,该模式就是整行。对???如果您知道模式将在特定字段中:请使用$ 1,$ 2等。例如gawk'$ 1〜/.*abc([0-9]+)xyz.*/'文件
ghostdog74 2009年
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.