How to create a function in a Bash shell script

By

Learn how to create and call a function in a Bash shell script to avoid repeating commands, plus how to loop over an array of folders to run it on each.

~~~

To create a function in a Bash script, write the function name followed by () and put the commands between curly braces. Then call it by writing its name, like any other command.

Here’s the real problem that got me there.

I was writing a shell script to automate things in my workflow to publish books.

I was doing things manually and generally I’m so lazy I could repeat things over and over again before automating, but today I had to do a very repetitive task and I wanted to do things right, so I just decided to write a script to help me and future me.

The script had to go into different folders and call npx 3 times in each, like this:

#!/bin/bash

cd c-handbook

npx honkit pdf ./ ../books/$(basename $PWD).pdf
npx honkit epub ./ ../books/$(basename $PWD).epub
npx honkit mobi ./ ../books/$(basename $PWD).mobi

cd ..

# and repeat this endlessly

cd css-handbook

npx honkit pdf ./ ../books/$(basename $PWD).pdf
npx honkit epub ./ ../books/$(basename $PWD).epub
npx honkit mobi ./ ../books/$(basename $PWD).mobi

cd ..

The part that npx is doing is all the same, for every folder. Copying it over and over is exactly the kind of duplication functions exist to remove.

Wrapping the repeated commands in a function

So I wrapped all the npx calls into a function, and I called that function instead:

#!/bin/bash

generate_book () {
  npx honkit pdf ./ ../books/$(basename $PWD).pdf
  npx honkit epub ./ ../books/$(basename $PWD).epub
  npx honkit mobi ./ ../books/$(basename $PWD).mobi
}

cd c-handbook
generate_book
cd ..

cd css-handbook
generate_book
cd ..

# ...and so on

Notice the parentheses in generate_book () stay empty. That’s just syntax. You don’t declare parameters there.

If you want to pass a value to a function, you add it after the name when you call it, like generate_book css-handbook, and inside the function you read it as $1. Here I didn’t need it, because the commands use $PWD, the current folder.

Looping over an array of folders

Then, while I was there, I also decided to create an array with all the book folders:

books=( "c-handbook" "css-handbook" "deno-handbook" "es5-to-next" "express-handbook" "html-handbook" "javascript-beginner-handbook" "linux-commands-handbook" "nextjs-handbook" "node-handbook" "python-handbook" "react-beginner-handbook" "svelte-handbook" "vue-handbook"  )

and to iterate over this array using a loop:

for i in "${books[@]}"
do
   echo $i
   cd $i
   generate_book
   cd ..
done

Final version:

#!/bin/bash

books=( "c-handbook" "css-handbook" "deno-handbook" "es5-to-next" "express-handbook" "html-handbook" "javascript-beginner-handbook" "linux-commands-handbook" "nextjs-handbook" "node-handbook" "python-handbook" "react-beginner-handbook" "svelte-handbook" "vue-handbook"  )

generate_book () {
  npx honkit pdf ./ ../books/$(basename $PWD).pdf
  npx honkit epub ./ ../books/$(basename $PWD).epub
  npx honkit mobi ./ ../books/$(basename $PWD).mobi
}

for i in "${books[@]}"
do
   echo $i
   cd $i
   generate_book
   cd ..
done

Two things to watch out for

My first version of this script started with #!/bin/sh. That worked on my Mac, but arrays are a Bash feature, not a POSIX shell feature. On Linux systems where sh points to dash, the books=( ... ) line is a syntax error. Use #!/bin/bash when your script uses arrays.

Also, be careful with cd inside a loop. If one folder doesn’t exist, cd $i fails, the script keeps running in the wrong directory, and the cd .. at the end of the iteration moves you one level too high. You can make the script stop instead:

cd "$i" || exit 1

If the cd fails, the script exits with an error, instead of generating books in the wrong place.

Tagged: CLI · All topics
~~~

Related posts about cli: