Using ‘if’ in a Bash script
Automation is the key to everything. If I have a two-step process, I’m going to automate it. If it’s a multi-step process, where the next step depends on the successful execution of the one before it, you can bet I’m going to automate it.
In a recent example, I wanted to view several Simplified Docbook files to see what the output looks like. Doing this involves an XSLT transformation and requires installing several dependencies, which seems a bit heavy when all I want to do is view the output as a PDF. Instead, I created a simple Bash script to process my Simplified Docbook file using tools that come installed by default on my system.
This is a three-step conversion: verify the file with xmllint
, convert the file with pandoc
, then convert to PDF with LibreOffice. Each step requires that the previous step in the chain runs successfully. This is an excellent example of how to use the if
statement in Bash.
Verifying the file with xmllint
Since I’m editing the Docbook file by hand, I want to be sure that my file is correct. The process for this is validation using a DTD file. I can use the xmllint
program to verify my Simplified Docbook articles using a copy of the DTD that I already saved on my system.
xmllint
can take several command line options, but I’m most interested in the --dtdvalid
option that takes a DTD file to verify against. To verify a single Simplified Docbook file, I can use this xmllint
command:
xmllint --dtdvalid $HOME/lib/docbook/sdocbook.dtd article.docbook
If the file contains any errors, xmllint
prints a message for each instance and exits with a non-zero status. If the file doesn’t contain any errors, xmllint
prints a clean version of the XML file. You can optionally suppress that output with --noout
.
Converting the file with pandoc
An easy way to process Simplified Docbook files is with the pandoc
program, which can read from and write to a long list of file types, including Docbook. However, to generate a PDF, pandoc
requires LaTeX, which I don’t have installed. But if all I want is to view the output of my Simplified Docbook article, I can use pandoc
to convert my file into a word processor format like LibreOffice ODT:
pandoc --from=docbook --to=odt article.docbook -o article.odt
If pandoc
successfully generates the output file without issues, it returns with a zero exit status. For any errors, pandoc
will return a specific nonzero code that indicates what went wrong.
Conditional execution with ‘if’
It’s important to know that the Simplified Docbook file is correct. If the file contains errors, pandoc
usually prints an error; but for small discrepancies, pandoc
might silently try to do the best it can. That doesn’t give me a good indication of what my article should look like.
So it’s important to only convert files that pass the xmllint
test. I can do that using a short Bash script that relies on the if
statement to perform conditional execution. The most basic format looks like this:
if command ; then commands ; fi
Since the xmllint
command exits with a zero status on success, we can use that as the test in the if
statement. My Bash script looks like this, where "$1"
is the first file listed when I run the script:
#!/bin/bash
if xmllint --noout --dtdvalid $HOME/lib/docbook/sdocbook.dtd "$1" ; then
pandoc --from=docbook --to=odt "$1" -o out.odt
fi
If successful, this generates a LibreOffice file called out.odt
with the results of my Simplified Docbook article.
I prefer to write this in a longer form that first runs the command, then uses if
with the $?
Bash variable, which returns the exit code of the previous command. When using if
this way, use the test
command, implemented internally in Bash using [
as a shorthand. For example, to run the xmllint
command and test if the previous exit code was zero, then run the pandoc
command, use this:
#!/bin/bash
xmllint --noout --dtdvalid $HOME/lib/docbook/sdocbook.dtd "$1"
if [ $? -eq 0 ] ; then
pandoc --from=docbook --to=odt "$1" -o out.odt
fi
Converting to PDF with LibreOffice
If I want to take another step and convert the LibreOffice file into a PDF file, I can use LibreOffice from the command line using the --convert-to
option. To convert a single file called out.odt
into a PDF file, you can run LibreOffice this way:
libreoffice --convert-to pdf out.odt
Let’s add this command to the Bash script, after doing the initial conversion using pandoc
. We can use another if
statement and the $?
variable to perform a second test like this:
#!/bin/bash
xmllint --noout --dtdvalid $HOME/lib/docbook/sdocbook.dtd "$1"
if [ $? -eq 0 ] ; then
pandoc --from=docbook --to=odt "$1" -o out.odt
if [ $? -eq 0 ] ; then
libreoffice --convert-to pdf out.odt
fi
fi
First, the script runs xmllint
to test the file. If that is successful, the script uses pandoc
to convert the file into LibreOffice ODT format. And if that conversion runs without errors, the script runs LibreOffice from the command line to convert the ODT file into PDF. Notice that the if
statements are nested inside each other.
If successful, this generates a new file called out.pdf
with the results of my Simplified Docbook article in PDF format.
Using Bash to automate everything
You can improve this Bash script with a little extra intelligence. For example, you might need to process more than one Simplified Docbook file. Instead of running the script multiple times, you could add a for
loop inside the Bash script to process all the files, one after the other. The general format of a Bash loop looks like this:
for variable in list ; do commands ; done
To loop through a list of files, we can add a for
loop that processes all the files on the command line. Bash provides a few ways to get all the command line arguments to a script; using "$@"
will preserve any spaces in filenames. So you can iterate over a list of files like this:
#!/bin/bash
for file in "$@" ; do
echo $file
done
As a final step, let’s integrate the for
loop into our script to process Simplified Docbook source files into PDF:
#!/bin/bash
for file in "$@" ; do
xmllint --noout --dtdvalid $HOME/lib/docbook/sdocbook.dtd "$1"
tmpfile=${file%.docbook}.odt
if [ $? -eq 0 ] ; then
pandoc --from=docbook --to=odt "$1" -o $tmpfile
if [ $? -eq 0 ] ; then
libreoffice --convert-to pdf $tmpfile
fi
fi
done
This uses an extra feature in Bash that can pull apart a variable using parameters. The ${file%.docbook}
expansion returns the value of the file variable then removes the last occurrence of .docbook
in the name. For example, if file
had the value article.docbook
, the ${file%.docbook}
expansion would give just article
. The script uses this to assign a new .odt
file extension with this line:
tmpfile=${file%.docbook}.odt
If I save my script as sdocbook
and make it executable with chmod +x sdocbook
, I can run the script to easily convert a list of Simplified Docbook files into PDF format. The output for each will be the base filename with a .pdf
extension, so article.docbook
will be converted to article.pdf
.
Writing a short Bash script can save typing lots of instructions at the command line. This sample Bash script collects three programs in one script, but only runs the next command if the previous program ran successfully. It’s a great way to automate and simplify the command line.