15

I am manipulating a large number of XML files scattered throughout a nested directory structure.

I tried the following:

$ find . -name "*.xml" -type f | xargs -- xmllint --format

The problem is that generates the formatted XML output on the screen, but doesn't change the file.

How can I change this command so that the actual file contents are changed?

рüффп
  • 1,677
  • 4
  • 27
  • 35
Harry
  • 1,843
  • 3
  • 13
  • 8

2 Answers2

29

This can be done from find directly using -exec:

find . -name "*.xml" -type f -exec xmllint --output '{}' --format '{}' \;

What's passed to -exec will be invoked once per file found with the template parameters {} being replaced with the current file name. The \; on the end of the find command just terminates the line.

The use of xargs isn't really necessary in this case because we need to invoke xmllint once per file as both the input and output file names must be specified within the same call.

xargs would be needed if the command being piped to from find was working on multiple files at a time and that list was long. You can't do that in this case, as you need to pass the single filename to the --output option of xmllint. Without xargs you could end up with a "Argument List too long" error if you are processing a lot of files. xargs also supports file replace strings with the -I option:

find . -name "*.xml" -type f | xargs -I'{}' xmllint --output '{}' --format '{}'

Would do the same as the find -exec command above. If any of your folders have odd chars in like spaces you will need to use the -0 options of find and xargs. But using xargs with -I implies the option -L 1 which means only process 1 file at a time anyway, so you may as well directly use find with -exec.

Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250
didster
  • 799
  • 5
  • 5
8

I typically attack these problems with a layer of indirection. Write a shell script that does what you want, and call that. I'd suggest as a start

#! /bin/sh
for file
do
   xmllint --format $file > $file.tmp && mv $file.tmp $file
done

The try it out on a file or two by hand, then you can replace it in the xargs

find . -name "*.xml" -type f | xargs -- xmltidy.sh
Julian
  • 899
  • 5
  • 5
  • This looks like a good approach if I should need to do more complex manipulation in the future. Thanks for the response. – Harry Oct 08 '12 at 08:05