An introduction to the most useful special and environment variables in Bash

Are you currently learning Bash? Have you seen things like $0 And $EUID and I wondered what they meant? Or what is the difference between $UID And $EUID? I’ll talk about them and more: what they do and why they’re important.
HTG Wrapped 2025: 24 days of technology
24 days of our favorite hardware, gadgets and technologies
Special settings
Special parameters are variables provided by Bash for specific purposes. There are less than 10, and the following seem the most useful to me.
Get the script path
Sometimes when you write a script you want to know its path. For example, when creating a help menu, it is customary to include the name of the script at the top. Here is the help menu for the ls command:
ls --help
You can access the (relative) path using the $0 special parameter. This is a POSIX standard, so you can use it in most shells:
echo "$0"
Then you can get the script name using $(basename $0):
However, sometimes $0 is unreliable; for example, the source /path/to/script.sh returns “bash” and not the script path. Instead, use the $BASH_SOURCE variable, which works almost identically, except without surprises. If you only use Bash, it’s the best choice, but it’s not portable to other shells.
Determining the exit status of a process
Each program returns a number as output. This is called the exit code and is used to make decisions when a command returns an error. For example, if you try to ls a non-existent directory, it returns a non-zero value.
A zero exit status means success; a non-zero value indicates a problem.
When you encounter a problem, you may want to pause execution and $? a special parameter provides the necessary exit code:
ls /system32
echo $?
There are several ways to detect and handle an exit code. The most obvious is to evaluate “$?” with a conditional statement:
if [[ $? -eq 0 ]]; then
# If the script executed successfully.
fi
The exit code usually means something and is program specific, but you can handle distinct values with a case statement:
case $? in
0) echo "OK";;
1) echo "Err";;
*) echo "Unhandled error";;
esac
One of my favorite approaches is to use a conditional statement without brackets:
if ls /system32; then
# Upon zero exit code.
echo "/system32 exists"
fi
But the most elegant solution is to use logical operators:
ls /system32 && echo "/system32 exist" # && executes on zero exit status.
ls /system32 || echo "/system32 does not exist" # || executes on non-zero exit status.
Access arguments
If you write a Bash script, you will invariably need to pass it values, i.e. arguments or positional parameters. Bash’s approach is a bit clunky, but it works. Examples are the easiest way to understand them:
foo() {
echo "$1" # First argument.
echo "$2" # Second argument.
}
foo "first" "second"
I passed arguments into a function, but they work exactly the same at the top level of your script. For example:
#!/usr/bin/env bash
# This is the top-level of your script.
echo "The first argument: $1"
echo "The second argument: $2"
You can then pass arguments into your script via the CLI: script.sh "first" "second".
Then, the special parameter “$@” represents all arguments as an array:
foo() {
for arg in "$@"; do
echo "$arg"
done
}
foo "first" "second"
The special parameter “$*” is the same as “$@”, except that it puts all arguments into a single string (if “$*” has double quotes):
at() {
printf '@: [%s]\n' "$@"
}
star() {
printf '*: [%s]\n' "$*"
}
at "one two" "three"
star "one two" "three"
You can see that “$@” prints both arguments on two lines, but “$*” prints them on a single line.
To get the number of arguments you can use the ${#@} Or ${#*} syntax:
foo() {
echo ${#@}
echo ${#*}
}
foo "one" "two"
Environment variables
Environment variables are values provided to running programs by the shell. For example, $HOME provides the path to the current user’s home directory.
Get User ID
Sometimes I need to know the current user id in a script. For example, recently I was interacting with a socket in the /run/user/1000 directory. The “1000” is the user ID, and if we want a robust script, we should not hardcode this value, because another user running it may have a different user ID.
To solve this problem we can use the $UID And $EUID variables. “$UID” displays the ID of the user running a binary. “$EUID” (effective user ID) is the user ID of the process executing a binary. Normally these are equal, but for setuid binaries they may differ. For example, when running something with sudo, they vary as the fork progresses through different stages.
ps --forest -eo cmd,euid,ruid | grep -C 1 '[s]udo'
I have “sudo sleep 600” running in the background and the prompt above shows the command columns, EUID (“1”) and UID (“2”) for any process running sudo. You can see that the EUID immediately becomes “0”, but the UID does not change until the final forked process (which is “dormant” running as root).
8 Linux Shell Tricks That Completely Change How Commands Work
The shell does much more than just run commands. Here’s how Bash extends your typing behind the scenes so you can write cleaner, more reliable commands.
What can we learn from it? Well, it doesn’t matter which one you use. Some say to use “$EUID” to check if you have root privileges, as those are the effective permissions. However, you can see in the image that the final “sleep” process has both the UID and EUID set to “0” (root), and the shell process is both set to “1000” (user). These represent either end of the fork process, where you issued the sudo request as well as the final execution process. Therefore, there is no useful distinction between them when it matters, but remember that they differ for setuid binaries during the fork process.
Get common user paths
In almost every script I write, I access the home directory. Less frequently, I need access to ~/.config Or ~/.local/share. Accessing typical locations in the filesystem is common, but hardcoding paths like this is bad practice because they can change. The recommended approach is to use the XDG directory specification, which is a set of standard variables provided by freedesktop.org.
env | grep XDG | sort -u
Not all variables are defined and some are not paths.
When using XDG variables, always set a reasonable default value:
export "${XDG_CACHE_HOME:=$HOME/.cache}"
This will also set the variable (if it’s not set) and make it available to any binaries or scripts you run.
The most common variables I use:
export "${XDG_CACHE_HOME:=$HOME/.cache}" # Semi-temporary data.
export "${XDG_CONFIG_HOME:=$HOME/.config}" # Configuration files.
export "${XDG_DATA_HOME:=$HOME/.local/share}" # Downloaded data, etc.
There are several others and I encourage you to learn them. You should use these locations instead of writing everything in /tmp or a custom directory in ~/.
How to get a cheat sheet for any command in Linux Terminal
Sometimes cheating is necessary.
In addition to these variables, many useful environment variables are available through the shell. The variables covered represent the ones I use most often. I couldn’t write Bash scripts without them. Conversely, hardcoding values will eventually lead to breakages. When you have hundreds of scripts, such failures become a maintenance nightmare. Therefore, it is best to infer as much as possible from elsewhere, preferably from standard variables, and the variables presented here today fit the bill perfectly.
In conclusion, learn and use as many standard variables as possible.
3 Bash Scripting Techniques Every Linux User Should Know
Unleash the power of Bash with these simple techniques.



