I have a number of xml files. I need to rename each based on the specific tag (say tag1="alpha") inside the xml.

I have figured out how to rename the file based on single occurance of the specific tag.

find . -maxdepth 1 -name '*.xml' -exec /rename.sh {} \;

tag1=$(sed 's/.*tag1="\([^"]*\).*/\1/; q' "$1")
mv -v "$1" "$tag1.xml"

The problem: some files have multiple tag1 tags, e.g. tag1="alpha" tag1="beta" tag1="omega". With these files I need to also create a copies of the same file, but with the other names, beta.xml, omega.xml.

I tried to cat with positional parameters but that didn't work (I am a beginner).

file structure example:

<root xmlns:d="http://www.apple.com/DTDs/DictionaryService-1.0.rng">

<div class="div-entry"><d:entry xmlns:d="1.0.rng" id="m_en_gbus0240000" tag1="cudgel" class="entry">
 <span class="hg x_xh0">
   <span role="text" d:syl="1" class="hw">cudg.el<d:syl/></span>
   <span prxid="cudgel_us_nv" prlexid="pron0014256.002" dialect="AmE" class="prx"> | <span d:prn="US" dialect="AmE" class="ph t_respell">ˈ;ke;je;l<d:prn/></span><span d:prn="IPA" soundFile="cudgel#_us_1" media="online" dialect="AmE" class="ph">ˈ;ke;d&#658;e;l<d:prn/></span> | </span>
 <span class="sg">
   <span id="m_en_gbus0240000.004" class="se1 x_xd0">
     <span role="text" class="posg x_xdh">
       <span d:pos="1" class="pos">
         <span class="gp tg_pos">noun </span>
     <span id="m_en_gbus0240000.005" class="msDict x_xd1 t_core">
       <span d:def="1" role="text" class="df">a short, thick stick used as a weapon<span class="gp tg_df">. </span><d:def/></span>
     <span class="gp x_xdt tg_se1"> </span>
   <span id="m_en_gbus0240000.008" class="se1 x_xd0">
     <span class="x_xdh">
       <span role="text" class="posg">
         <span d:pos="2" class="pos">
           <span class="gp tg_pos">verb </span>
         <span class="infg"><span class="gp tg_infg">(</span><span tag1="cudgels", <span tag1="cudgeling"<span class="pr"/>, <span tag1="cudgeled"<span class="pr"/>; <span class="lg"><span class="ge">British </span></span><span tag1="cudgels", <span tag1="cudgelling", <span tag1="cudgelled"<span class="gp tg_infg">) </span></span>
       <span role="text" class="gg"><span class="gp tg_gg">[</span>with <span class="sy">object</span><span class="gp tg_gg">] </span></span>
     <span id="m_en_gbus0240000.011" class="msDict x_xd1 t_core">
       <span d:def="2" role="text" class="df">beat with a cudgel<d:def/></span>
       <span role="text" class="gp tg_df">: </span>
       <span role="text" class="eg">
         <span class="ex"> they would lie in wait and cudgel her to death</span>
         <span class="gp tg_eg">. </span>
   <span class="gp tg_sg"> </span>
 <span class="subEntryBlock x_xo0 t_phrases">
   <span class="gp x_xoLblBlk ty_label tg_subEntryBlock">PHRASES </span>
   <span id="m_en_gbus0240000.022" class="subEntry x_xo1">
     <span class="x_xoh">
       <span role="text" tag1="cudgel one's brains "
       <span class="vg"><span class="gp tg_vg">(</span>also <span id="m_en_gbus0240000.037" tag1="cudgel one's brain"<span class="gp tg_vg">) </span></span>
     <span id="m_en_gbus0240000.024" class="msDict x_xo2 t_core">
       <span role="text" class="lg">
         <span class="ge">British </span>
       <span role="text" class="df">think hard about a problem</span>
       <span role="text" class="gp tg_df">: </span>
       <span role="text" class="eg">
         <span class="ex"> she cudgeled her brains, trying to decide what had caused such an about-face</span>
         <span class="gp tg_eg">. </span>
     <span class="gp x_xot tg_subEntry"> </span>
   <span id="m_en_gbus0240000.025" class="subEntry x_xo1">
     <span role="text" tag1="take up the cudgels "
     <span id="m_en_gbus0240000.026" class="msDict x_xo2 t_core">
       <span role="text" class="df">start to defend or support someone or something strongly</span>
       <span role="text" class="gp tg_df">: </span>
       <span role="text" class="eg">
         <span class="ex"> there was no one else to take up the cudgels on their behalf</span>
         <span class="gp tg_eg">. </span>
 <span role="text" class="etym x_xo0">
   <span class="gp x_xoLblBlk ty_label tg_etym">ORIGIN </span>
   <span class="x_xo1"><span class="dg"><span class="date">Old English </span></span><span class="italic">cycgel</span>, of unknown origin<span class="gp tg_etym">.</span></span>
<div class="div-entry"><div>

  • Lovely. Is it only the tag1 attributes for <div/> or do you need them for <span/> too? Any other elements with a tag1 attribute we need to consider? Commented Jul 20, 2022 at 21:22
  • Only <div/> and <span/>.
    – Simon Ante
    Commented Jul 20, 2022 at 21:33
  • You're using the d namespace. Can you please provide the namespace declarations at the top of the file. Ideally provide the full file rather than a snippet. Commented Jul 20, 2022 at 21:38
  • This? xmlns:d="http://www.apple.com/DTDs/DictionaryService-1.0.rng"
    – Simon Ante
    Commented Jul 20, 2022 at 21:50
  • 1
    do not parse XML with sed – sed is mostly useful for regular expressions, XML is not a regular language, so even if this works because the input actually is a much restricted subset of what would be well-formed and valid XML here, you're just making your life unnecessarily hard by using the wrong tool for a theoretically easy job – finding specific tags with specific attributes. Commented Jul 20, 2022 at 22:42

2 Answers 2


Why not use xmlstarlet to extract tag1 phrases and process its output in a shell script loop.

xmlstarlet select accepts multiple pathnames as input so it can be used with the more efficient {} + variant of find's -exec operand. Note that -exec … {} + always evaluates as true so it won't abort on a non-zero exit status. To list the filenames along with the tag1 phrases in converted form, for example:

# shellcheck shell=sh disable=SC2016

find . -maxdepth 3 -type f -name '*.xml' -exec \
  xmlstarlet select --text -t \
    --var tag1nodes='//@tag1[name(..)="span" or name(..)="div"]' \
    --var allowchars -o 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' -b \
    --var tag1cvt -o 'translate(normalize-space(),translate(.,$allowchars,""),"_")' -b \
    -m 'set:distinct(dyn:map($tag1nodes,$tag1cvt))' \
      -s 'A:T:-' '.' \
      -f -o "${ofs:-$(printf '\t')}" -v '.' -n \
  {} +

Notes on the xmlstarlet select command

  • select uses the namespace definitions in the root element of the first input file (and predefines xsl, saxon, and those from EXSLT), any additional namespaces referenced must be defined with -N options
  • the tag1nodes variable collects the tag1 phrases (attribute values) from div or span elements, here assumed to be in the default namespace or in the null ditto
  • the EXSLT dyn:map function maps each phrase to a string by evaluating the tag1cvt argument, an XPath expression given as a text string, which
    • strips leading, trailing, and redundant whitespace
    • replaces non-alphanumeric characters with _ (underscore), assuming a C locale
  • EXSLT's set:distinct eliminates duplicates
  • sort order is: Ascending, Text, unspecified case order
  • no output is generated if no relevant tag1 values are found (in which case select returns 1)
  • adding a -C option before -t will list the XSLT 1.0 stylesheet select uses
  • the --var definitions here are added for readability and easily exchanged for the dyn:map args
  • pathnames from find are assumed to be without
    • ' (single quote) characters, due to a known bug in select
    • whitespace, if a shell script processing the output is using the default IFS value with read

Partial sample output:

./dir-b/fum.xml cudgels
./dir-b/fum.xml take_up_the_cudgels
./fee.xml       cudgel_ones_brain
./fee.xml       cudgel_ones_brains
./fee.xml       cudgeled
./fee.xml       cudgeling
./fee.xml       cudgelled
./fee.xml       cudgelling

The file you posted isn't XML but xmlstarlet can recover its contents (omit -q to list errors):

xmlstarlet -q format -R junk > file.xml
  • I've just added the d namespace declaration that was missing from the question. You may need to add this into your answer too Commented Mar 10 at 11:04
  • 1
    @ChrisDavies: It makes no difference to my answer: the script doesn't look for anything in the d namespace, and after your edit the file is still not XML so the last paragraph is still relevant.
    – urznow
    Commented Mar 10 at 17:07
  • That's fine. Just wanted to give you a heads-up. I've already +1 your answer Commented Mar 10 at 17:11

There seems to be two separate parts to this question. The first one is to find the contents of all tag1 occurrences. The second part is how to use them to rename your xml file. The conditions for the second part is not entirely clear from the current post.

This is a solution only to the first part.

$ sed 's/tag1/\ntag1/g' input_file | sed  -n '/tag1/p' | cut -d '"' -f2
cudgel one's brains 
cudgel one's brain
take up the cudgels

You must log in to answer this question.

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