如何使用 GREP、 REGEX 或 PERL 提取模式下的字符串

I have a file that looks something like this:

<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>

I need to extract anything within the quotes that follow "name=", i.e., content_analyzer , content_analyzer2 and content_analyzer_items.

I am doing this on a Linux box, so a solution using sed, perl, grep or bash is fine.

转载于:https://stackoverflow.com/questions/5080988/how-to-extract-string-following-a-pattern-with-grep-regex-or-perl

csdnceshi55
~Onlooker Thanks everyone for the useful comments. I apologize for the XML not being properly formatted. I deleted some tags for simplification.
9 年多之前 回复
csdnceshi69
YaoRaoLov I feel that it would be wrong not to link to stackoverflow.com/questions/1732348/…
9 年多之前 回复
weixin_41568196
撒拉嘿哟木头 no need to be shy, welcome here!
9 年多之前 回复

8个回答

Since you need to match content without including it in the result (must match name=" but it's not part of the desired result) some form of zero-width matching or group capturing is required. This can be done easily with the following tools:

Perl

With Perl you could use the n option to loop line by line and print the content of a capturing group if it matches:

perl -ne 'print "$1\n" if /name="(.*?)"/' filename

GNU grep

If you have an improved version of grep, such as GNU grep, you may have the -P option available. This option will enable Perl-like regex, allowing you to use \K which is a shorthand lookbehind. It will reset the match position, so anything before it is zero-width.

grep -Po 'name="\K.*?(?=")' filename

The o option makes grep print only the matched text, instead of the whole line.

Vim - Text Editor

Another way is to use a text editor directly. With Vim, one of the various ways of accomplishing this would be to delete lines without name= and then extract the content from the resulting lines:

:v/name=/d
:%s/\v.*name\="([^"]+)".*/\1

Standard grep

If you don't have access to these tools, for some reason, something similar could be achieved with standard grep. However, without the look around it will require some cleanup later:

grep -o 'name="[^"]*"' filename

A note about saving results

In all of the commands above the results will be sent to stdout. It's important to remember that you can always save them by piping it to a file by appending:

> result

to the end of the command.

csdnceshi72
谁还没个明天 grep -Po 'look-ahead \K capture' made my day. Slick.
3 年多之前 回复
csdnceshi53
Lotus@ On OS X, simply install grep via homebrew and use that instead of the default one. It should work.
5 年多之前 回复
csdnceshi79
python小菜 I found this article: "Perl Regex Removed From Grep in Mountain Lion" (dirtdon.com/?p=1452 ). I'm on Yosemite myself, but the article seems to be valid for that as well.
5 年多之前 回复
csdnceshi57
perhaps? its is cited in the manual as extension. Not sure how documented it is, but well, I'm on OS X as well and works here.
5 年多之前 回复
csdnceshi79
python小菜 The -P flag does not seem to be supported on OS X: grep [-abcDEFGHhIiJLlmnOoqRSsUVvwxZ] [-A num] [-B num] [-C[num]] [-e pattern] [-f file] [--binary-files=value] [--color=when] [--context[=num]] [--directories=action] [--label] [--line-buffered] [--null] [pattern] [file ...]
5 年多之前 回复
csdnceshi57
perhaps? Thank you. That is a character class, when it begins with ^ that means it matches anything except its contents. So [^"] means every character that's not a quote. I didn't use it in the latter answer in favor of the unready version, .*?. The previous was greedy, so I used that class to match everything not a quote with the intention of stopping on the first quote, which is the same as matching anything "ungreedly" up to a quote. Hope this helps understanding it, let me know if I can clarify better some part.
6 年多之前 回复
csdnceshi67
bug^君 +1 for describing the command. Would appreciate it if you could update your answer to explain the "[...]" part of the regex.
6 年多之前 回复
csdnceshi58
Didn"t forge Thanks so much. This works perfectly!
9 年多之前 回复
csdnceshi57
perhaps? Williamson: certainly sir, a lot of helpful information here. I think the reason I kept the \K (after researching on it) and removed the .* was the same: make it look pretty (simpler). And I've never thought in using .*? instead of the "traditional way" I learned from somewhere. But un-greedy here really makes sense. Thanks Dennis, best wishes.
9 年多之前 回复
csdnceshi55
~Onlooker Why would I be angry? Without the .*, you can do grep -Po '(?<=name=").*?(?=")'. The \K can be used for shorthand, but it's really only needed if the match to its left is variable length. In cases like this, the reason for using lookarounds is fairly obvious. Ungreedy operations look a little neater ([^"]* versus .*? and you don't have to repeat the anchor character. I don't know about speed. That depends a lot on the context, I think. I hope that's helpful.
9 年多之前 回复
csdnceshi57
perhaps? Williamson, great. I updated the answer accordingly, but left both .* aside, I hope you don't get angry with me. I'd like to ask, do you see any benefits from un-greedy match over "anything except ""? Don't take this as a fight, I'm just curious and I'm not a regex expert. Also, the \K tip, really nice. Thanks Dennis.
9 年多之前 回复
csdnceshi55
~Onlooker Lookarounds (in GNU grep): grep -Po '.*name="\K.*?(?=".*)'
9 年多之前 回复

An HTML parser should be used for this purpose rather than regular expressions. A Perl program that makes use of HTML::TreeBuilder:

Program

#!/usr/bin/env perl

use strict;
use warnings;

use HTML::TreeBuilder;

my $tree = HTML::TreeBuilder->new_from_file( \*DATA );
my @elements = $tree->look_down(
    sub { defined $_[0]->attr('name') }
);

for (@elements) {
    print $_->attr('name'), "\n";
}

__DATA__
<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>

Output

content_analyzer
content_analyzer2
content_analyzer_items

The regular expression would be:

.+name="([^"]+)"

Then the grouping would be in the \1

this could do it:

perl -ne 'if(m/name="(.*?)"/){ print $1 . "\n"; }'

Here's a solution using HTML tidy & xmlstarlet:

htmlstr='
<table name="content_analyzer" primary-key="id">
<type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
<type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
<type="global" />
</table>
'

echo "$htmlstr" | tidy -q -c -wrap 0 -numeric -asxml -utf8 --merge-divs yes --merge-spans yes 2>/dev/null |
sed '/type="global"/d' |
xmlstarlet sel -N x="http://www.w3.org/1999/xhtml" -T -t -m "//x:table" -v '@name' -n

If you're using Perl, download a module to parse the XML: XML::Simple, XML::Twig, or XML::LibXML. Don't re-invent the wheel.

csdnceshi53
Lotus@ Note that example OP gave is not well-formed (<type="global" for instance), so most of XML parsers just complain and die.
9 年多之前 回复

Oops, the sed command has to precede the tidy command of course:

echo "$htmlstr" | 
sed '/type="global"/d' |
tidy -q -c -wrap 0 -numeric -asxml -utf8 --merge-divs yes --merge-spans yes 2>/dev/null |
xmlstarlet sel -N x="http://www.w3.org/1999/xhtml" -T -t -m "//x:table" -v '@name' -n

If the structure of your xml (or text in general) is fixed, the easiest way is using cut. For your specific case:

echo '<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>' | grep name= | cut -f2 -d '"'
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐