Passing arrays to bash functions

Short version: yes, you can pass an array to a Bash function. You can also manipulate the array contents within the function to pass information back. It is easy and has been supported since Bash version 4.3, I believe.

Long version: I recently watched a talk by some very knowledgeable people about Bash, where they delved deep into its internals and quirks. At one point, they discussed passing information to and from functions without creating subshells. The solution became quite convoluted, and I was surprised because the whole time I was thinking, “just use nameref“.

Out of curiosity, I searched online, but unfortunately, the internet is full of responses like “doesn’t work,” “Bash can’t do that” and many variations of “just pass all the values of the array to the function as arguments and piece them together again inside the function” (which is a terrible solution since you lose the keys). There are a few posts here and there suggesting local -n as a solution, but they are rare, and especially on sites like Stack Overflow, they are not the top answers.

In a nutshell what we are going to do is pass a reference to an array to a function (think “pointers” or “symlinks” if that helps).

Relevant parts of the bash man page for declare and local:

declare -n

Give each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references, assignments, and attribute modifications to name, except for those using or changing the -n attribute itself, are performed on the variable referenced by name’s value. The nameref attribute cannot be applied to array variables.

local

For each argument, a local variable named name is created, and assigned value. The option can be any of the options accepted by declarelocal can only be used within a function; it makes the variable name have a visible scope restricted to that function and its children.

So, in summary, we can use local to define variables with a scope limited to the function they are defined in, and local accepts all the options that declare supports.

It seems the “The nameref attribute cannot be applied to array variables.” part of the declare definition causes a lot of confusion or deters people from trying to use it for referencing arrays.
What it means is that you can’t do a local -n my_array=() (i.e. applying the nameref attribute to an array), but local -n my_array is fine (where my_array is a variable with the nameref attribute which can also point to an array).

Enough theory, let’s get down to practical examples.

We will create a function called do_stuff that:

  • takes the name of an array as argument $1
  • reads the length key from the array
  • add a random key to the array with a random number the length of the length key previously read

Then we will create an array outside of the function with some keys/values, pass it to the do_stuff function, and then output the contents

So this shows us that the do_stuff function could read from the array (the length value), and write to the array (add the random number key/value), and the changes were applied to the array outside of the function. (where we did the declare -p). Bonus points for not needing a subshell.

Using this “trick” allows us to pass more complex information to a function, and especially receive more complex information from a function.

There is one caveat: you can’t use the same name for the array inside the function as well as outside. I wouldn’t advise doing this anyway for readability reasons, as the variable’s scope can become confusing. If you try it you get the following output:

Oddly I often noticed the following statement on Stack Overflow about nameref:

This only works if the array is defined as a global
Nope, works just as fine passing an array locally scoped to a function to another function this way.

(I prefer using a main() function like in this example to avoid global variables unless explicitly defined)

So, there you have it: an easy way to pass an array to a function in bash, no weird looping over values. And a better way to receive information from an array than the byte return value and parsing the output of the function.

How to colorize manpages

I’m surprised I’ve never posted this here before. Turning manpages from monochrome to color is super easy.

There are a few LESS_TERMCAP_*  environment variables you can adjust. Here is a list of useful ones to change

I prefer to only set them for man, so I put this little function in my ~/.bashrc

 

Bash function for easily watching logs and colorizing the output

Another useful bash function I have on my servers. It’s a wrapper around tail -F  and ccze . It will look for a log file (prepends /var/log/ to the patch if it can’t find it), and pipes it into ccze for colorizing the output. Handy if you find yourself watching logs. I mostly use it for dhcp/tftp/mail where I don’t have a huge amount of traffic (i.e. can watch it in real time) and am expecting an event/log entry.

Usage:

Using regex comparision in bash and BASH_REMATCH

Bash supports regular expressions in comparisons via the =~ operator.  But what is rarely used or documented is that you can use the ${BASH_REMATCH[n]}  array to access successful matches (back-references to capture groups). So if you use parentheses for grouping ()  in your regex, you can access the content of that group.

Here is an example where I am parsing date placeholders in a text with an optional offset (e.g. |YYYY.MM.DD|+2 ). Storing the format and offset in separate groups:

 

 

 

Multiply floats by 10,100, … in bash

A short one today. Bash can only handle integer numbers and not floats, so when someone searches the internet on how to use math on floats in bash the solution they find is usually “use bc” and looks something like this:

Or if they want the result to be an integer:

It’s a fine solution, and readable (which can mean a lot for people maintaining scripts). But if all you want to do is multiply by 10,100,1000, … you can achieve this faster with a bit of string manipulation:

It just splits the number into two strings, and assembles it again with the decimal shifted. Have a look at substring_removal and substring_expansion for more examples on how to modify strings in bash. I’d highly suggest either sticking this in a separate function, or commenting the code since it isn’t necessarily obvious what is going on

Since it is all pure bash and doesn’t need to spawn external commands, it quicker (not that bc  is slow, but if you are doing a lot of calculations, it can add up). I know what you are thinking “if your goal is speed, you shouldn’t be using bash”, that doesn’t mean we can’t write efficient code.