How to create a function in a Bash shell script
By Flavio Copes
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.
Related posts about cli: