2

I have a file like the following

<g> Good wheatear </g> other parts of line 
<g> The farm land is to be sold </g> other parts of line
<g> knock knock </g> other parts of line 

I want my output to be like this:

<g> Good wheatear </g> 
<g> The farm land is to be sold </g>
<g> knock knock </g>

i.e. print the content between <g> and </g> tags including the tags

I have tried this command:

awk '/<s>/, /<\/s>/' trsTest.txt

But it prints the whole line.

How to print the content between the tags ?

3
  • 3
    I guess there is a typo in your Q and that the awk command you tried used <g> rather than <s>
    – mattb
    Jun 5 at 16:08
  • 1
    You say you want to print the content between the tags, but your example desired output shows the tags included along with the content between.
    – mattb
    Jun 5 at 16:19
  • 2
    If other parts of line can also include <g> and or </g> then you should include that in your sample input/output as those cases would break most possible solutions.
    – Ed Morton
    Jun 5 at 18:34
8

With awk it could be:

$ awk -v FS="</?g>" '{print $2}' trsTest.txt
 Good wheatear
 The farm land is to be sold
 knock knock

Or if you want to keep the tags:

$ awk -v FS="</g> " '{print $1 FS}' trsTest.txt
<g> Good wheatear </g>
<g> The farm land is to be sold </g>
<g> knock knock </g>
2
  • Wouldn't your second approach only work if the line starts with the <g> tag? Consider The joke starts with <g> knock knock </g> (although the OPs input does not contain such examples, they didn't rule it out either ...)
    – AdminBee
    Jun 7 at 12:34
  • @AdminBee: you're right, it wouldn't work Jun 7 at 16:40
7

You can simply use GNU grep and print only the matched part (-o) of the lines:

grep -o '<g>.*<\/g>' trsTest.txt

The pattern needs to be between single quotes to prevent the shell from expanding the characters (like the *)

This first command will produce:

<g> Good wheatear </g>
<g> The farm land is to be sold </g>
<g> knock knock </g>

If you don't want the tags included, then you can do this:

sed 's/.*<g>\(.*\)<\/g>.*/\1/' trsTest.txt

The ways it's working is as follows:

  • match everything up to and including the opening <g> tag
  • I remember the stuff in between the above using \( and \)
  • match anything after the closing <\g> tag to the end of the line
  • Then replace with the remembered content \1

This second command will produce:

 Good wheatear
 The farm land is to be sold
 knock knock
0
5

If this is XML you can use an XML parser such as xmlstarlet. (If it's HTML you can still use xmlstarlet but you have to warn it that the structure may be broken.)

I've added a bounding <root/> element to make the input legal XML:

<root>
  <g> Good wheatear </g> other parts of line
  <g> The farm land is to be sold </g> other parts of line
  <g> knock knock </g> other parts of line
</root>

And then the command to pick out the </g> elements and values is

xmlstarlet sel --template --match '//g' --copy-of '.' --nl file.xml

Output

<g> Good wheatear </g>
<g> The farm land is to be sold </g>
<g> knock knock </g>

The advantage of xmlstarlet over generic text parsing tools such as sed or grep is that it understands XML structure, so if the input file changes slightly while still remaining valid XML, xmlstarlet will still be able to parse it.

4
  • It doesn't really matter if it actually is XML. The given document looks like an XML fragment, and that's enough for xmlstarlet to work with as if it had been XML.
    – Kusalananda
    Jun 5 at 18:10
  • 1
    @Kusalananda even xmlstarlet -H needs a reasonably well formed document, doesn't it? I can't test right now but I would have expected that three sequential <g>...</g> elements without an enclosing parent element would be invalid
    – roaima
    Jun 5 at 21:39
  • I was merely applauding your approach, attaching a root node to the existing fragment. Doing that turns the fragment into a well-formed XML document, no matter if the original document was actually XML or not.
    – Kusalananda
    Jun 6 at 15:59
  • Oh ok. Thank you :)
    – roaima
    Jun 6 at 17:46
2

With awk:

awk '{if (match($0, /<g>.*<\/g>/)) 
print substr($0,RSTART,RLENGTH); }' input

In this command, built-in function match() searches for the regex.

From awk manual:

The match() function sets the predefined variable RSTART to the index. It also sets the predefined variable RLENGTH to the length in characters of the matched substring.

After finding index and length of regex in current input record($0), the built-in function substr(target,start,length) gets the expected output.

You have tried with awk '/<g>/, /<\/g>/' file. This command uses range pattern where two patterns are separated by a comma. Becasuse range pattern selects records, you are getting whole line.

1

if the tags are always the same, to include the tags, use sed

sed 's/\(.*>\).*/\1/' trsTest.txt

output is:

<g> Good wheatear </g>
<g> The farm land is to be sold </g>
<g> knock knock </g>

Updated to Quasimodo's more efficient code block

0
1

Using GNU sed we chop the string until the substring <g> shows up in the front.

sed -n '
 ​/^<g>/!s|<g>|\n&|;/\n/D
 ​/^<g>/s|</g>|&\n|;/\n/P
' file

Method using Perl:

perl -ne ' print m{(<g>.*?</g>).*(.)}s' file
python3 -c 'import sys
ifile,s1,s2 = sys.argv[1:]
with open(ifile) as fh:
 for l in fh:
   p1 = l.find(s1)
   p2 = l.find(s2,p1)
   if p1 > -1 and p2 > -1:
     print(l[p1:p2+len(s2)])
' file '<g>'  '</g>'
1

Using GNU awk:

With gensub() function (GNU awk) you can print the content between <g> and </g> tags including the tags:

awk '{print gensub(/(<g>.*<\/g>)(.*)/,"\\1","g")}' file
<g> Good wheatear </g>
<g> The farm land is to be sold </g>
<g> knock knock </g>

or not including the tags:

awk '{print gensub(/(<g>)(.*)(<\/g>)(.*)/,"\\2","g")}' file
 Good wheatear
 The farm land is to be sold
 knock knock
2
  • 2
    The GNU awk user's guide: gensub() provides an additional feature that is not available in sub() or gsub(): the ability to specify components of a regexp in the replacement text. This is done by using parentheses in the regexp to mark the components and then specifying ‘\N’ in the replacement text, where N is a digit from 1 to 9. So the first parenthesis here gives the content you want. And "g" is for global. Jun 6 at 9:04
  • 1
    So "\\1" is backreference. But to quote backreference seems strange. I've already upvoted the answer. Thanks for responding. Jun 6 at 9:24
0

Another way with sed:

sed -n '
    s/<\/*g>/\n/g
    s/^\n/<g>/
    s/\n.*/<\/g>/
    p
' data
  • Change any sequence of <g> and </g> to a new line: s/<\/*g>/\n/g
  • replace the first new line with an open <g> tag: s/^\n/<g>/
  • replace anything comes after the latter new line with a </g> closing tag: s/\n.*/<\/g>/
  • print the line : p

If you'd like to remove the tag too, the sed command become:

sed -n '
    s/<\/*g>/\n/g
    s/^\n//
    s/\n.*//
    p
' data

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.