Appending environment variables to PATH the easy way (Linux)
Tags: bash, linux
Published: 2023-10-04 18:12:44

## Appending to PATH variable
In Linux, you have seen something like this
```bash
export SOME_ENV="..."
PATH="$SOME_ENV:$PATH"
```
which append the env variable value to `PATH`.

It is really annoying to do this on the command line. So Linux guys suggest putting this in the `~/.bashrc` file.

Excellent. 

But it can become a real mess quickly because there is all other important stuff in `.bashrc`. <br>
I don't feel it right to have everything bash-related bumped into one file.

## Quick fix
So I decided to move all of my configured env variables to a separate file instead of messing things up in `.bashrc`.
I called it `.bash_envs` and placed it in my home directory (where `.bashrc` is located).

> _I also separated bash functions and aliases into 
> `.bash_functions` and `.bash_aliases` respectively with the same idea in mind_

In order to work properly, `.bashrc` needs to know about and execute the `.bash_envs` file.

So to accomplish this I wrote the following code in `.bashrc`:
```bash
#Exported env definitions.
if [ -f ~/.bash_envs  ]; then
    . ~/.bash_envs
fi
```

Also `.bash_envs` file needs to be executable so I typed a quick:
```bash
chmod +x .bash_envs
```

And the content of `.bash_envs` includes something like this:
```bash
#!/bin/bash
...

# java
export JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64"
PATH="$JAVA_HOME/bin:$PATH"

# pnpm 
export PNPM_HOME="$HOME/.local/share/pnpm"
PATH="$PNPM_HOME:$PATH"

...
```

Perfect.

But to be honest, it is really annoying to type `PATH=...` every time an env variable is exported.

So I came up with a better idea.

## The improvement

If I could somehow store the required paths in something like an array, then join the items with a colon symbol (`:`), and finally append it to `PATH`? 

Wait a second, there are arrays in `bash` and I could do something like this:
```bash
path_envs=(
    "$JAVA_HOME/bin"
    "$PNPM_HOME"
    ...
)
```

But actually, I think it's better to _append_ every item immediately after the env variable `export`, because otherwise you can easily miss an item and you have to manually check if everything is as you expect.

Then the code should become something like this:
```bash
path_envs=() # defining an empty array

# java
export JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64"
path_envs+=("$JAVA_HOME/bin") # appending java path to array

# pnpm
export PNPM_HOME="$HOME/.local/share/pnpm"
path_envs+=("$PNPM_HOME") # appending pnpm path to array
```

So far so good. But how to actually join items with a colon `:`? 

There are plenty of solutions but the easiest one I found was this: 
```bash
PATH="$( IFS=":" ; echo "${path_envs[*]}" ):$PATH"
```

Don't worry I will explain step by step the above code snippet.

### Explanation

#### $( Dollar Single Parentheses )
So the `$( ... )` parentheses part means that everything inside will be executed in a _subshell_ and the result will be returned as a string.

For example:
```bash
echo "Hello, my name is $( whoami )"
# => "Hello, my name is ivo"
```

#### Internal Field Separator (IFS)
The content inside the paratheses is more interesting. The first part (before the semicolon `;`) sets the special variable **Internal Field Separator** (`IFS`) with a value of colon `:`. 
This in short means it treats `:` as a separator between words/items in a string instead of whitespace which is the default.

> _More detailed information with examples about `IFS` can be found [here](https://www.baeldung.com/linux/ifs-shell-variable)._

The second part just prints the `path_envs` array of its items separated by `IFS`. One notable detail is that `path_envs[*]` is used instead of `path_env[@]` for expanding the array, because `[*]` returns a single shell-word with all of the elements of the array separated by `IFS` (as we need), while `[@]` leads to each element of the array being treated as a separate shell-word.

As a result, the subshell should return something like:
```bash
/usr/lib/jvm/java-21-openjdk-amd64:/home/ivo/.local/share/pnpm...
```

### Final result
All of that is finally concatenated with the old value of `PATH` and simply that was the work we should do.

In the end, my `.bash_envs` file looked something like this:
```bash
#!/bin/bash

path_envs=()

# java
export JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64"
path_envs+=("$JAVA_HOME/bin")

# pnpm
export PNPM_HOME="$HOME/.local/share/pnpm"
path_envs+=("$PNPM_HOME")

# adding path_envs to PATH
PATH="$( IFS=":" ; echo "${path_envs[*]}" ):$PATH"
```