4

Ubuntu 14.04 Trusty Tahr.

Suppose I have directory called 'testmag' which may contain 100s of xml files and directories which in turn contain many xml files as well. I don't know names of any xml files but I know one of them contains tag <dbname>....</dbname>.

Now how to find the file containing the aforementioned tag and grep the tag's value as output in terminal

4 Answers 4

4

Using a proper XML parser to parse XML:

shopt -s globstar nullglob
for file in **/*.xml; do 
    dbname=$(xmlstarlet sel -t -v '//dbname' "$file")
    [[ -n "$dbname" ]] && printf "%s\t%s\n" "$file" "$dbname"
done
3

Here is a solution with find that will also output the filenames of files containing a match:

find . -name "*.xml" -exec grep '<dbname>' {} \;             \
                     -exec echo -e {}"\n" \;                 \
                     | sed 's/<dbname>\(.*\)<\/dbname>/\1/g'

Explanation

  1. find . -name "*.xml" find all xml files recursively from current directory
  2. -exec grep '<dbname>' {} \; on each file search for pattern <dbname>
  3. -exec echo -e {}"\n" \; echo filename + new line (-e option makes echo interpret \n)
  4. | sed 's/<dbname>\(.*\)<\/dbname>/\1/g' pipe output to sed to print only the field contained between the <dbname></dbname> tags.

NOTE1: you can format output in your echo -e ... to have results for each file clearly laid out, e.g. by adding new lines, or lines of underscore, whatever suits your need.

NOTE2: path to each file will be given relatively to . (e.g. ./subfolder1/file.xml). If you want absolute path, go for find $PWD -name ....

4
  • This exactly answers my query, perfect !!! Thanks @Valentin B.
    – Vicky Dev
    Oct 21, 2016 at 18:49
  • Well as far as question is concerned, your post answers it, no doubt, but just one query out of curiosity, if you don't mind. With above find command if I want to look for only <dbname>...</dbname> tag having <![CDATA[ as it's content's starting string and ignore the rest of the <dbname>... matches, how can I achieve it ?
    – Vicky Dev
    Oct 21, 2016 at 18:55
  • Hi Vicky, simply add the pattern in your grep command, ... -exec grep '<dbname><![CDATA[' {} \; ... Oct 24, 2016 at 9:21
  • This assumes that the dbname node's start and end tag are on the same line.
    – Kusalananda
    May 16, 2021 at 8:22
2

Using find with xq:

find testmag -type f -name '*.xml' -exec xq -r '..|(.dbname? // empty)' {} +

This would find all regular files with names matching *.xml in or below the testmag directory. For batches of these, xq would be called to extract the value of every dbname node found in those documents.

xq is a jq-like XML parser that is distributed with yq from https://kislyuk.github.io/yq/


Would you need the filenames of all XML files that have this node, you could use

find testmag -type f -name '*.xml' -exec xq -e '..|(.dbname? // empty)' {} \; -print

... although this would be a fair bit slower than just extracting the value of the node as we need to invoke xq once for each file.

2
  • Hi, one question, please. jq, yq, xq, these tools has no option for themselves for recursion? May 16, 2021 at 8:32
  • 1
    @αғsнιη You mean to recurse into directories? No, they are parsers. The task of providing the tools with filenames is left to the calling shell.
    – Kusalananda
    May 16, 2021 at 8:33
0

Suppose we have directory XMLS containing these files:

cat XMLS/file1
foo bar <dbname>target</dbname> baz
foo foo

cat XMLS/file2
<name>notarget</name>

I would use this command:

grep -r '<dbname>' XMLS/ | sed 's/.*<dbname>\(.*\)<\/dbname>.*/\1/'
target

Which, as you can see, returns the value inside the <dbname> tags. And not the value inside the <name> tags.


The -r flag for grep searches recursively.

sed strips the string of everything except the value target.

6
  • Can I use . for recursive search inside current directory ?
    – Vicky Dev
    Oct 21, 2016 at 11:08
  • @VickyDev Yes, . works fine. Oct 21, 2016 at 11:09
  • Why is target on second line ? Is it required in code ?
    – Vicky Dev
    Oct 21, 2016 at 11:11
  • @VickyDev What? No. That is the actual output from the command. Oct 21, 2016 at 11:12
  • @maulinglawns You should add g at the end of your sed instructions otherwise in case of multiple matches from grep only the first pattern will be processed. Oct 21, 2016 at 12:36

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.