20

patterns.txt:

"BananaOpinion"
"ExitWarning"
"SomeMessage"
"Help"
"Introduction"
"MessageToUser"

Strings.xml

<string name="Introduction">One day there was an apple that went to the market.</string>
<string name="BananaOpinion">Bananas are great!</string>
<string name="MessageToUser">We would like to give you apples, bananas and tomatoes.</string>

Expected output:

"ExitWarning"
"SomeMessage"
"Help" 

How do I print the terms in patterns.txt that are not found in Strings.xml? I can print the matched/unmatched lines in Strings.xml, but how do I print the unmatched patterns? I'm using ggrep (GNU grep) version 2.21, but am open to other tools. Apologies if this is a duplicate of another question that I couldn't find.

5 Answers 5

30

You could use grep -o to print only the matching part and use the result as patterns for a second grep -v on the original patterns.txt file:

grep -oFf patterns.txt Strings.xml | grep -vFf - patterns.txt

Though in this particular case you could also use join + sort:

join -t\" -v1 -j2 -o 1.1 1.2 1.3 <(sort -t\" -k2 patterns.txt) <(sort -t\" -k2 strings.xml)
3
  • this is quite elegant.. smart!
    – XXL
    Aug 16, 2016 at 11:12
  • If you have multiple input files (e.g. Strings1.xml and Strings2.xml), you will also need the -h flag on the first grep.
    – jayhendren
    Mar 14, 2018 at 16:41
  • @jayhendren - yeah but not all greps support that option. If you have multiple input files I don't see why you couldn't simply cat them all and pipe the result to grep. Mar 14, 2018 at 19:43
5

The best approach is probably what @don_crissti suggested, so here's a variation on the same theme:

$ grep -vf <(grep -Po 'name=\K.+?"' Strings.xml) patterns.txt
"ExitWarning"
"SomeMessage"
"Help"

This basically is the inverse of @don_crissti's approach. It uses grep with Perl Compatible Regular Expressions (-P) and the -o switch to print only the matching part of the line. Then, the regex looks for name= and discards it (\K), and then looks for one or more characters until the first " (.+?"). This results in the list of patterns present in the String.txt file which is then passed as input to a reverse grep (grep -v) using process substitution (<(command)).

2

I would use cut, probably. That is, if, as it appears, you know where to expect the quoted string you're looking for.

If I do:

{   cut  -sd\" -f2 |
    grep -vFf- pat
}   <<\IN
#   <string name="Introduction">One day there was an apple that went to the market.</string>
#   <string name="BananaOpinion">Bananas are great!</string>
#   <string name="MessageToUser">We would like to give you apples, bananas and tomatoes.</string>
IN

...after saving my own copy of your example patterns.txt in pat and running the above command the output is:

"ExitWarning"
"SomeMessage"
"Help"

cut prints to stdout only the second " double-quote -delimited -field for each delimiter-matched line of input and -suppresses all others.

What cut actually prints at grep is:

Introduction
BananaOpinion
MessageToUser

grep searches its named file operand for lines which -v don't match the -Fixed strings in its - stdin pattern -file.

If you can rely on the second "-delimited field as the one to match, then it will definitely be an optimization over grep -Perl mode by just matching -Fixed strings and only tiny portions of them because cut does the heavy lifting - and it does it fast.

2
for p in $(cat patterns.txt); do if ! grep $p strings.xml &>/dev/null; then echo $p; fi; done

it's easy to understand but has the downtime of spawning multiple grep processes, one for each line in patterns.txt.

1

another way is to put patterns.txt and Strings.xml into one list and find unique rows

cat patterns.txt Strings.xml | grep -oFf patterns.txt | sort | uniq -u

explanation:

cat patterns.txt Strings.xml puts everything into one list. grep -oFf patterns.txt removes garbage on each line. sort self-explanatory. sort all lines. uniq -u prints only unique lines.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .