We’ve seen how bash shell scripts work in Linux, and allow you to automate complex actions. Bash functions are essential to shell scripts – they help keep code clean and modular. While bash isn’t a traditional programming language, there are many similarities, even though it doesn’t have some more advanced functionality than modern programming languages, like the foreach statement.
Today, we’ll look at how bash functions work, how to pass arguments to them, and how we can manipulate these arguments inside functions.
Purpose of Bash Functions
While bash functions serve the purpose of all functions in any other programming language, they have another, more specific role in this context. Namely that they can be thought of as “commands” in their own right. We saw an example of this when we talked about aliases in bash, where we saw that we couldn’t use aliases with dynamic arguments, and we used bash functions to get around that limitation.
Bash functions allow us to create our own command with its own optional parameters, and this means that functions have to be able to accommodate a wide range of inputs.
No Fixed Signatures
A consequence of the requirement that bash functions must be able to accept dynamic inputs, is that we don’t have the kind of fixed function signatures we see in other languages. The reason some languages have fixed function signatures is so that the compiler can catch errors early, and also for efficiency and safety.
But bash isn’t compiled – it’s interpreted, and that has advantages and disadvantages. It means that errors in bash scripts will only be revealed once the corresponding statement is processed, unlike other programs where they’ll be caught during compilation. But because it’s interpreted, it makes more sense for bash signatures to be dynamic.
And so bash functions can double up as bash commands that can be defined in either the bash environment or in the script files themselves. And for this reason, they don’t have fixed signatures.
Using Bash Function Arguments
Here’s an example of a bash function that uses arguments:
my_function() {
echo "First argument: $1"
echo "Second argument: $2"
}
Within the function, you can access these arguments by using the variables $1, $2, etc. These refer to the positions of the arguments when they’re passed to the function. So for example:
my_function "hello" "world"
In this command, I’m calling the function I just defined, and the passed two arguments to it. The first is “hello”, and the second is “world” in the first and second positions respectively. Based on this, the output of the above command is:
As expected, it printed the first and second arguments.
Iterating Through the Arguments
The above example of a bash function was simple because the contents of the function explicitly printed the first and second arguments. But we’ve seen that bash allows us to pass as many, or as few arguments as we want to any function. So how will a function work when it doesn’t know how many arguments it’s going to receive, and in which order?
Luckily, we have a variable called “$@” within the function that allows us to iterate through its arguments one at a time. We can access each of the arguments within a for loop like this:
print_all_args() {
for arg in "$@"; do
echo "$arg"
done
}
In the above example, the “print_all_args” function creates a for loop that goes through all the arguments that have been supplied and then prints them one by one. The following statement:
for arg in "$@"
Iteratively places the next argument in the $arg variable, and prints it. So the following command:
print_all_args apple banana cherry orange raspberry
Has this output:
As expected, the function prints all arguments, regardless of how many or how few were passed. We can use these loops to parse complex command functions with variable parameters.
More Realistic Logging Function
The above code examples are nice, but not very practical. Let’s look at a more concrete use case, where the dynamic functions of bash play a role.
Let’s say we want to create a logging function that will output the log messages we send with the date and time stamps. Such a function can’t predict in advance what kind of data will sent to it. It could be error messages, debugging messages, or it could even contain custom variables belonging to the bash environment.
Consider the following:
log() {
local level="$1"
shift
local message="$*"
echo "[$(date +%F\ %T)] [$level] $message"
}
Now see the output for the following input:
In the above screenshot, we send various kinds of inputs to the function, and it accepts them by printing the date and time, followed by the first argument in square brackets and then the rest of the message.
It even works with user variables. For example, here is an input that sends a pre-defined user variable to the function:
log DEBUG "User input: $user_input"
Here’s what the output looks like:
In the above screenshot, I’ve defined a user variable for debugging purposes and given it the value “In here!”. Now I can send this message to the log function with the “DEBUG” tag, and it’ll show the output you see. Note how we can use any message with any number of words, even including user variables.
You don’t even need to use quotation marks for the string portion. So the following will work as well and give the same output:
log DEBUG User input: $user_input
We’re calling the function just as if “log” was a command with parameters. We can use the above function to send the output to a log file if we want or display it on the screen.
Using the “Shift” Keyword
The above function also highlights a useful keyword while dealing with bash functions – shift. The “shift” keyword discards the current first keyword and shifts all the remaining arguments up one level. So basically:
- $1 will be discarded
- $2 will become $1 ($1=$2)
- $3 will become $2 ($2=$3)
- etc..
This might seem like a niche functionality, but it allows us to process arguments one by one and then discard them when we no longer need them. For example, in the log function, we assign the first argument to the “level” keyword, and then we don’t need it anymore. So we “shift” all the arguments left and this allows us to create a neat echo statement that outputs the rest without needing to discard the first argument.
In fact, the “shift” keyword lets us easily write parsers – functions that consume arguments one by one in a sequential order. This makes sense given the fact that functions can double up as keywords. By using “shift”, we can always focus on the first argument ($1) in a continuous loop by discarding it after processing.
Conclusion
Even though bash functions aren’t strictly typed, this matters less than usual because bash isn’t a traditional programming language, and neither is it compiled. So the efficiency gains from having a fixed type function wouldn’t materialize anyway. But it does give them huge flexibility, allowing them to process variable arguments just like a command with parameters. We even use functions as a substitute for the “alias” command when we need more utility.

I’m a NameHero team member, and an expert on WordPress and web hosting. I’ve been in this industry since 2008. I’ve also developed apps on Android and have written extensive tutorials on managing Linux servers. You can contact me on my website WP-Tweaks.com!





Leave a Reply