1. Comments on your code
Your example input file is not a well-formed XML document. An XML document must have a single root element, but your example has two. What tool did you use to generate this file? You should ask yourself how it went wrong.
(If you're just writing it "by hand" in some other program using a bunch of print
statements, you might ask yourself why you are using XML at all rather than something simple like CSV. You clearly can't be intending to process these file using standard XML tools, since most XML tools will refuse to process ill-formed documents.)
In the find
command:
find generatedSchema/myApp/db/ -name *.hbm.xml
the pattern *.hbm.xml
would be expanded by the shell (rather than the find
command) if there happened to be any files matching the pattern in the directory where this command is being run. So single-quote the pattern to make sure this can't happen:
find generatedSchema/myApp/db/ -name '*.hbm.xml'
You might also consider adding the argument -type f
to ensure that the names returned all refer to regular files. (It's not very likely in your case that someone will name a directory foo.hbm.xml
, but adding a -type
argument to a find
command is often good practice.)
In this line you don't need to escape the dots:
javaFile=${myFile/%\.hbm\.xml/.java}
The pattern in ${VAR/pat/rep}
here is not a regular expression, so dots are not special. For example:
$ FOO='a.b.c'
$ echo ${FOO/./|}
a|b.c
You use regular expressions to transform your XML into Java source. Now, as I'm sure you're aware, you can't reliably process XML using regular expressions. Perhaps what you've got here works because you control the format of these XML files. But really you should be using an XML-aware tool. See section 2 below.
You use a temporary file to store the output of your sed
command, and then load it into a variable using a loop over its lines.
You don't need this loop, because you can load the contents of a file into a variable by writing:
VARIABLE=$(<filename)
And you don't need the temporary file either, because you can load the output of a command into a variable by writing
VARIABLE=$(command)
So just write
str=$(sed -n "...")
You could choose better variable names than myFile
(why yours?) and str
(what's in the string?).
In your last sed
command, you use the expression s/pattern/\0\n$str/
to append $str
after every match of pattern
. You might consider using p;c
instead so that you can simplify the pattern (it doesn't have to match the whole line any more):
sed -i -e "/^private static final long serialVersionUID/{p;c\
$str
}"
You expand variables myFile
and javaFile
in your script in contexts like this:
sed -i -e "..." $javaFile
This will go wrong if the variable javaFile
contains spaces. You should double-quote the variable to ensure that it's treated as a single argument to sed
even if it contains spaces. (Perhaps you know in this case that it can't contain spaces, but double-quoting variables containing filenames is still a good habit to get into.)
sed -i -e "..." "$javaFile"
I don't like the way you modify the Java source code in-place. This would be dangerous if this is your original Java source code, because a bug in your script could overwrite the Java source code with nonsense and break your build. (But since I can't see the rest of your build system I can't tell if this concern is justified.)
If the whole Java file is automatically generated, wouldn't it be better to do it in one go rather than piecemeal like this? I have a feeling that there are more improvements to be made to this code generation process.
2. Using a programming language with XML support
If you need to process data from XML documents, then use a programming language together with an XML parsing library.
For example, here's how you might rewrite this same script in Python. First, you'd change the way you generate your .hbm.xml
files so that they are well-formed XML documents. Let's suppose that they now look like this:
<properties>
<property name="address1C" type="string">
<column name="address1_c" length="50" />
</property>
<property name="address2C" type="string">
<column name="address2_c" length="50" />
</property>
</properties>
Then you might write the following Python program:
#!/usr/bin/python
import fileinput
import sys
import xml.etree.ElementTree
def main():
# Check command-line argument.
hbm_xml_filename = sys.argv[1]
ext = '.hbm.xml'
if not hbm_xml_filename.endswith(ext):
raise RuntimeError("Filename {} doesn't end with {}"
.format(hbm_xml_filename, ext))
# Load property names and maximum lengths from input file.
tree = xml.etree.ElementTree.parse(hbm_xml_filename)
properties = []
for property in tree.findall('property[@type="string"]'):
column = property.find('column')
properties.append(dict(name = property.attrib['name'],
length = column.attrib['length']))
# Rewrite Java in-place to add "MAX_name = length;" declarations.
java_filename = hbm_xml_filename.replace(ext, '.java')
for line in fileinput.input(files=[java_filename], inplace=True):
sys.stdout.write(line)
if line.startswith('private static final long serialVersionUID'):
sys.stdout.write('\n')
for p in properties:
sys.stdout.write(
'@SuppressWarnings({{"UnusedDeclaration"}}) '
'public static final int MAX_{name} = {length};\n'
.format(**p))
if __name__ == '__main__':
main()
You'll see that I've used the xml.etree.ElementTree
module to parse and query the XML, I've used an XPath query to find all properties with the attribute type="string"
, and I've used the fileinput
module to update the Java source code in-place.
Then you can run this Python program from your shell script:
find generatedSchema/myApp/db/ -type f -name '*.hbm.xml' |
while read property_file; do
add_property_lengths.py "$property_file"
done
3. Addendum
You said you were having some difficulty getting VAR=$(command)
to work. You should be able to verify that it works in general by testing it in the shell:
$ NUMBERS=$(yes '' | head | nl -ba)
$ echo $NUMBERS
1 2 3 4 5 6 7 8 9 10
Clearly we have different versions of sed
(I'm using the BSD-ish version that comes with Mac OS X, so I had to specify -E
for extended regular expressions and change the regular expression syntax a bit) but the following works for me:
VAR=$(sed -E -n '/ *<property name="([^"]+)" type="string">/{
N
s/ *<property name="([^"]+)" type="string">\n *<column name="[^"]+" length="([0-9]+)".*/ @SuppressWarnings({"UnusedDeclaration"}) public static final int MAX_\1 = \2;/
p
}' "$myFile")
so you should be able to figure out how to make something like it work for you (if you must do it this way).