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

Posted

技术标签:

【中文标题】如何使用 sed、awk 或 gawk 仅打印匹配的内容?【英文标题】:how to use sed, awk, or gawk to print only what is matched? 【发布时间】:2010-12-16 13:30:37 【问题描述】:

我看到很多关于如何使用 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

【问题讨论】:

【参考方案1】:

我的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

【讨论】:

谢谢,一旦我使用 * 而不是 +,这对我也有用。 ...以及打印比赛的“p”选项,我也不知道。再次感谢。 我不得不逃脱 + 然后它对我有用:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p' 那是因为你没有使用现代 RE 格式,因此 + 是一个标准字符,你应该用 , 语法来表达它。您可以添加使用 -E sed 选项来触发现代 RE 格式。检查 re_format(7),特别是说明的最后一段 developer.apple.com/library/mac/#documentation/Darwin/Reference/… 除了-E 选项之外,您还可以使用\1,\(代替*+)来计算一个或多个重复。您可以指定下限或上限,或同时指定两者。【参考方案2】:

您可以使用 sed 来执行此操作

 sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
-n 不打印结果行 -r 这样就可以避免捕获组 parens() 的转义。 \1捕获组匹配 /g全局匹配 /p打印结果

我为自己写了一个tool,让这更容易

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

【讨论】:

这是迄今为止最好、解释最清楚的答案! 通过一些解释,更好地理解我们的问题出了什么问题。谢谢! 1.您不需要-n 和/p。你只需要其中之一。 2. global 没有意义,因为 sed 是贪婪的,所以无论有没有你都会得到相同的结果: sed -r 's/.*abc([0-9]+)xyz.*/\1 /' @AvihaiMarchiano 我刚刚测试过,您似乎对/g 标志是正确的。但是删除 -n/p 会导致我没有输出任何输出。【参考方案3】:

我使用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 的原因。【参考方案4】:

如果您的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 只支持“贪婪”匹配...所以第一个 .* 将匹配该行的其余部分。除非我们可以使用否定字符类来实现非贪婪匹配...或具有 Perl 兼容的 sed 版本或其正则表达式的其他扩展,否则我们无法从模式空间中提取精确的模式匹配(一行)。

【讨论】:

你可以这样组合你的两个sed命令:sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p' 以前不知道 grep 上的 -o 选项。很高兴知道。但它会打印整个匹配项,而不是“(...)”。所以如果你在 "abc([[:digit:]]+)xyz" 上匹配,那么你会得到 "abc" 和 "xyz" 以及数字。 感谢您提醒我grep -o!我试图用sed 来做到这一点,并为我需要在某些行上找到多个匹配项而苦苦挣扎。我的解决方案是***.com/a/58308239/117471【参考方案5】:

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

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

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


使用grep,您可以使用后视和前瞻:

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

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

这会检查出现在 abcxyz 中的模式 [0-9]+ 并仅打印数字。

【讨论】:

【参考方案6】:

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

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

示例输入文件的输出将是

12345

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

【讨论】:

如果您需要(或想要)使用 gawk,这是一个聪明、可行的解决方案。您注意到了这一点,但要明确一点:非 GNU awk 没有 gensub(),因此不支持这一点。 不错!但是,最好使用match() 访问捕获的组。请参阅my answer。【参考方案7】:

如果你想选择行然后去掉你不想要的位:

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> 

更新: 显然,如果你的实际情况比较复杂,REs 将需要我修改。例如,如果您总是在开头和结尾处将一个数字埋在零个或多个非数字中:

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

【讨论】:

有趣...所以没有一种简单的方法可以应用复杂的正则表达式并返回 (...) 部分中的内容吗?因为虽然我看到你在这里首先使用 grep 然后使用 sed 做了什么,但我们的实际情况比删除“abc”和“xyz”要复杂得多。使用正则表达式是因为我要提取的文本的任一侧都可能出现许多不同的文本。 如果 RE 真的很复杂,我敢肯定 更好的方法。也许如果您提供更多示例或更详细的描述,我们可以调整我们的答案以适应。【参考方案8】:

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

【讨论】:

【参考方案9】:

为什么还需要匹配组

gawk/mawk/mawk2 'BEGIN FS="(^.*abc|xyz.*$)"  ($2 ~ /^[0-9]+$/) print $2'

让FS收走线的两端。

如果 $2(FS 未吞下的剩余部分)不包含非数字字符,那就是您打印出来的答案。

如果您格外谨慎,请确认 $1 和 $3 的长度都为零。

** 在实现零长度后编辑的答案 $2 会绊倒我以前的解决方案

【讨论】:

【参考方案10】:

有一段来自 awk 频道的标准代码,名为“FindAllMatches”,但它仍然是非常手动的,从字面上看,只是while()match()substr()、更多substr() 的长循环,然后冲洗并重复。

如果您正在寻找有关如何仅获取匹配部分的想法,但是对于每行匹配多次或根本不匹配的复杂正则表达式,请尝试以下操作:

mawk/mawk2/gawk 'BEGIN  srand(); for(x = 0; x < 128; x++ )  

    alnumstr = sprintf("%s%c", alnumstr , x) 
 ; 
 gsub(/[^[:alnum:]_=]+|[AEIOUaeiou]+/, "", alnumstr) 
                       
                    # resulting str should be 44-chars long :
                    # all digits, non-vowels, equal sign =, and underscore _

 x = 10; do  nonceFS = nonceFS substr(alnumstr, 1 + int(44*rand()), 1)

  while ( --x );   # you can pick any level of precision you need.
                    # 10 chars randomly among the set is approx. 54-bits 
                    #
                    # i prefer this set over all ASCII being these 
                    # just about never require escaping 
                    # feel free to skip the _ or = or r/t/b/v/f/0 if you're concerned.
                    #
                    # now you've made a random nonce that can be 
                    # inserted right in the middle of just about ANYTHING
                    # -- ASCII, Unicode, binary data -- (1) which will always fully
                    # print out, (2) has extremely low chance of actually
                    # appearing inside any real word data, and (3) even lower chance
                    # it accidentally alters the meaning of the underlying data.
                    # (so intentionally leaving them in there and 
                    # passing it along unix pipes remains quite harmless)
                    #
                    # this is essentially the lazy man's approach to making nonces
                    # that kinda-sorta have some resemblance to base64
                    # encoded, without having to write such a module (unless u have
                    # one for awk handy)


    regex1 = (..);  # build whatever regex you want here

    FS = OFS = nonceFS;

  $0 ~ regex1  

    gsub(regex1, nonceFS "&" nonceFS); $0 = $0;  

                   # now you've essentially replicated what gawk patsplit( ) does,
                   # or gawk's split(..., seps) tracking 2 arrays one for the data
                   # in between, and one for the seps.
                   #
                   # via this method, that can all be done upon the entire $0,
                   # without any of the hassle (and slow downs) of 
                   # reading from associatively-hashed arrays,
                   # 
                   # simply print out all your even numbered columns
                   # those will be the parts of "just the match"

如果您还运行另一个 OFS = ""; $1 = $1; ,现在不需要 4 参数 split()patsplit(),这两者都是特定于查看正则表达式 seps 是什么的,现在是整个 $0 的字段在 data1-sep1-data2-sep2-.... 模式中,.....所有而 $0 看起来与您第一次阅读该行时完全相同。直接向上的print 将逐字节地与读取时立即打印相同。

一旦我使用代表有效 UTF8 字符的正则表达式对其进行了极端测试。 mawk2 大约花了 30 秒左右的时间来处理一个 167MB 的文本文件,其中包含大量的 CJK unicode,一次全部读入 $0,然后启动这个拆分逻辑,导致 NF 约为 175,000,000,每个字段都是 1-single ASCII 或多字节 UTF8 Unicode 字符。

【讨论】:

【参考方案11】:

你可以用shell做到这一点

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

【讨论】:

【参考方案12】:

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

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

【讨论】:

这里不输出数值([0-9+]),而是输出整行。【参考方案13】:
gawk '/.*abc([0-9]+)xyz.*/' file

【讨论】:

这似乎不起作用。它打印整行而不是匹配。 在您的示例输入文件中,该模式是整行。正确的???如果您知道模式将在特定字段中:使用 $1、$2 等。例如 gawk '$1 ~ /.*abc([0-9]+)xyz.*/' file

以上是关于如何使用 sed、awk 或 gawk 仅打印匹配的内容?的主要内容,如果未能解决你的问题,请参考以下文章

使用 sed 或 awk 按照匹配模式打印一行

匹配文件中的字符串并使用 sed 或 awk 打印整个值

AWK ( 一 )

如何使用 awk 打印匹配的正则表达式模式?

awk工具(三剑客)

awk grep 或 sed:如何匹配两个文件