The Bash logo

Using ‘if’ in a Bash script

0

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.