我看到很多关于如何使用 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
我的 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
您可以使用 sed 来执行此操作
sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
-n 不打印结果行
-r 这使得你没有逃脱捕获组parens()。
\1 捕获组匹配
/g 全局匹配
/p 打印结果
我为自己写了一个tool,让这更容易
rip 'abc(\d+)xyz' '$1'
/g
标志是正确的。但是删除 -n
或 /p
不会为我打印任何输出。
我使用 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
如果您的 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'
您可以将 awk
与 match()
一起使用来访问捕获的组:
$ 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
这会检查出现在 abc
和 xyz
中的模式 [0-9]+
并仅打印数字。
perl 是最简洁的语法,但如果您没有 perl(我理解并非总是存在),那么使用 gawk 和正则表达式组件的唯一方法是使用 gensub 功能。
gawk '/abc[0-9]+xyz/ { print gensub(/.*([0-9]+).*/,"\\1","g"); }' < file
示例输入文件的输出将是
12345
注意: gensub 替换整个正则表达式(在 // 之间),因此您需要在 ([0-9]+) 之前和之后放置 .* 以消除替换中数字之前和之后的文本。
match()
访问捕获的组。参见my answer。
如果要选择行,请去掉不需要的位:
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]*$//'
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
为什么甚至需要匹配组
gawk/mawk/mawk2 'BEGIN{ FS="(^.*abc|xyz.*$)" } ($2 ~ /^[0-9]+$/) {print $2}'
让FS收走线路的两端。
如果 $2(FS 没有吞下的剩余部分)不包含非数字字符,那就是您打印出来的答案。
如果您格外谨慎,请确认 1 美元和 3 美元的长度都为零。
** 实现零长度后编辑的答案 $2 会绊倒我以前的解决方案
有一段来自 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 字符。
你可以用外壳来做
while read -r line
do
case "$line" in
*abc*[0-9]*xyz* )
t="${line##abc}"
echo "num is ${t%%xyz}";;
esac
done <"file"
对于 awk。我会使用以下脚本:
/.*abc([0-9]+)xyz.*/ {
print $0;
next;
}
{
/* default, do nothing */
}
([0-9+])
,而是输出整行。
gawk '/.*abc([0-9]+)xyz.*/' file
不定期副业成功案例分享
+
然后它对我有用:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
-E
选项之外,您还可以使用\{1,\}
(代替*
或+
)来计算一个或多个重复。您可以指定下限或上限,或同时指定两者。