Skip to content

Aliases and Environment Variables

Aliases make interactive commands shorter. Environment variables pass named values to programs. They both shape the shell experience, but they behave very differently. An alias is expanded by the current interactive shell before a command runs. An environment variable is inherited by child processes when the shell starts them.

This article assumes Bash on Ubuntu Server LTS. It builds on Command Syntax and Help Documentation, especially quoting, type, and PATH.

Alias, Shell Variable, Environment Variable

Concept Used by Inherited by child processes? Example
Alias Current interactive Bash command line No alias ll='ls -lah'
Shell variable Current shell Not unless exported project='demo api'
Environment variable Shell and child processes Yes export EDITOR='nano'

An exported variable is still a shell variable in the parent shell, but Bash marks it to be copied into the environment of future child processes. A child process receives a copy. It cannot edit the parent shell's environment after it has started.

Working with Aliases

List current aliases:

alias
alias ll 2>/dev/null

Ubuntu's default interactive Bash configuration may define ll; a minimal server may not.

Create aliases:

alias ll='ls -lah'
alias c='clear'
type ll

Expected output:

ll is aliased to `ls -lah'

There must be no spaces around =. Quote the alias value so the shell stores it as one value at definition time.

Alias expansion is not a full parameter system. It works well for fixed command prefixes:

alias grep='grep --color=auto'

It is not the right tool when you need to put an argument in the middle of a command. Use a function for that:

mkcd() {
    mkdir -p -- "$1" && cd -- "$1"
}

This example is useful for a lab, but a production-quality function should handle missing arguments and errors explicitly.

Bypassing or Removing an Alias

Check all meanings of a name:

type -a ls

Bypass an alias:

command ls
\ls

command ls bypasses shell functions as well as aliases and then uses the normal built-in or PATH lookup. A leading backslash prevents alias expansion for that word.

Remove one alias:

unalias c

Avoid unalias -a unless you intentionally want to clear every alias in the current shell.

Shell Variables

Create a shell variable:

project='demo api'
echo "$project"

Output:

demo api

Assignment syntax is strict:

project='demo'

This is an assignment. But:

project = demo

is parsed as a command named project with arguments = and demo.

Variable names are case-sensitive. By convention, variables intended for the environment are uppercase, while local shell variables in scripts are often lowercase.

Exporting to the Environment

A non-exported shell variable is not inherited:

project='demo api'
bash -c 'echo "child sees: <$project>"'

Output:

child sees: <>

Export it:

export project
bash -c 'echo "child sees: <$project>"'

Output:

child sees: <demo api>

Create and export in one step:

export EDITOR='nano'

Inspect variables:

printenv EDITOR
echo "$EDITOR"
declare -p EDITOR

The commands answer slightly different questions:

  • printenv EDITOR prints the value from the process environment.
  • echo "$EDITOR" prints the value after Bash expands the shell variable.
  • declare -p EDITOR is Bash-specific and shows attributes, including whether the variable is exported.

Remove a variable from the current shell:

unset project

This does not change processes that were already started.

Environment Inheritance

Environment is copied from parent to child when a new program starts:

Bash with APP_MODE=staging exported
  -> python app.py receives APP_MODE=staging
  -> bash receives APP_MODE=staging
       -> env prints that child's environment

A child can change its copy without changing the parent:

export APP_MODE='staging'
bash -c 'APP_MODE=testing; echo "child=$APP_MODE"'
echo "parent=$APP_MODE"

Output:

child=testing
parent=staging

Set a variable for one command only:

LC_ALL=C sort names.txt

This sets LC_ALL for that sort process and its children, not for the current shell after the command exits.

Common Environment Variables

Inspect a few common values:

printenv HOME USER SHELL PATH LANG

Important variables include:

  • HOME: the user's home directory.
  • USER or LOGNAME: login username, depending on the program.
  • SHELL: the user's configured login shell. It does not prove the currently running shell.
  • PATH: command search path.
  • LANG and LC_*: locale settings for language, sorting, and formatting.
  • TZ: time zone used by some programs for that process.
  • EDITOR and VISUAL: preferred text editor for programs that honor them.

Check the current shell separately:

echo "$BASH_VERSION"
ps -p $$ -o comm=

EDITOR and VISUAL

Some tools open an editor based on VISUAL or EDITOR:

export EDITOR='nano'
export VISUAL="$EDITOR"

Not every program honors these variables. Check the ENVIRONMENT section of that program's manual page.

TZ, LANG, and Reproducible Output

Compare time output:

TZ=UTC date
date

The first command uses UTC only for that process. It does not change the system timezone.

Locale can change sort order and messages. Scripts that need bytewise, predictable sorting often use:

LC_ALL=C sort names.txt

PATH: Command Search Path

Print PATH:

echo "$PATH"

It is a colon-separated list. Bash searches left to right for an executable when the command name contains no /.

Find what will run:

command -v ls
type -a ls

Add a personal bin directory without losing the existing path:

mkdir -p "$HOME/.local/bin"
export PATH="$HOME/.local/bin:$PATH"

Verify:

case ":$PATH:" in
    *":$HOME/.local/bin:"*) echo "present" ;;
    *) echo "missing" ;;
esac

Warning

Do not replace PATH accidentally with export PATH="$HOME/.local/bin". That removes system directories and can make normal commands unavailable. Do not add . or world-writable directories to PATH, especially for root.

If Bash has cached command locations and you changed PATH, clear the hash table:

hash -r

Persisting Settings

Aliases and variables typed at a prompt disappear when the shell exits. Persist them in the correct startup file.

For Bash on Debian and Ubuntu:

  • Interactive non-login shells normally read /etc/bash.bashrc and ~/.bashrc.
  • Interactive login shells read /etc/profile, then the first readable file among ~/.bash_profile, ~/.bash_login, and ~/.profile.
  • Ubuntu's default ~/.profile may source ~/.bashrc for Bash, but check the file instead of assuming.
  • Non-interactive shells, cron jobs, and systemd services do not automatically behave like your terminal.

Safely add an alias to ~/.bashrc:

cp -a ~/.bashrc ~/.bashrc.before-alias
nano ~/.bashrc
bash -n ~/.bashrc

Add this line near other aliases:

alias ll='ls -lah'

bash -n checks syntax without executing the file. Test in a new shell:

bash
type ll
exit

You can load the changed file into the current shell with:

. ~/.bashrc

Only source files you trust. Sourcing executes shell code in your current shell.

For environment variables used by services, do not rely on ~/.bashrc. Use the service manager's environment mechanism, such as a systemd unit Environment= line or an EnvironmentFile=, when that topic is covered.

Security and Production Notes

  • Environment variables are visible to the process and often to diagnostic tools run by the same user or by root. Do not treat them as a secret store.
  • Be careful with LD_PRELOAD, LD_LIBRARY_PATH, PYTHONPATH, and similar variables. They can change what code a program loads.
  • sudo commonly resets or filters environment variables according to policy. Do not assume your shell environment survives privilege escalation.
  • Keep aliases modest on shared servers. If your workflow depends on many personal aliases, you may make mistakes on a clean system.
  • Prefer explicit commands in scripts. Aliases are mainly for interactive use.

Why LD_PRELOAD Actually Matters

LD_PRELOAD is an environment variable read by the dynamic linker, not by Bash. When a dynamically linked program starts, the linker loads every shared library named in LD_PRELOAD before the program's normal libraries, and functions in that library take priority over the same-named functions the program would otherwise call. This is a legitimate debugging mechanism — it is how tools like strace-adjacent profilers and memory checkers such as libasan inject themselves without recompiling the target program.

The same mechanism is what makes it a production risk. A malicious or compromised shared library set via LD_PRELOAD can override common libc functions — read, open, getuid, functions used for password checks — and change what a program observes or does, invisibly to the program itself:

printf '%s\n' 'int getuid(void) { return 0; }' > /tmp/fake.c
gcc -shared -fPIC -o /tmp/fake.so /tmp/fake.c
LD_PRELOAD=/tmp/fake.so id -u
0

id -u normally reports the real numeric user ID. Here it reports 0 for a non-root user, because LD_PRELOAD replaced the getuid call the program relies on to identify who is running it. No file permission was bypassed and no exploit was needed — the program's own libc calls were quietly redirected before it ever ran. This is why sudo and other privilege-aware tools sanitize LD_PRELOAD and related variables out of the environment before executing anything: an inherited LD_PRELOAD combined with elevated privilege turns a debugging feature into a way to hijack a privileged process's view of the system. Never set LD_PRELOAD, LD_LIBRARY_PATH, or accept them from an untrusted environment for anything that runs with elevated privilege, and treat an unexplained LD_PRELOAD in a service's environment as an incident to investigate, not a quirky configuration.

Practical Scenario: A Script Works in Your Terminal but Fails in Cron

Problem: a maintenance script runs cleanly when you execute it by hand over SSH, but the same script, scheduled with cron, fails or behaves differently.

Reproduce the cron environment instead of guessing:

crontab -l
*/5 * * * * /home/admin/bin/cleanup.sh >> /home/admin/cleanup.log 2>&1

Cron runs this line through /bin/sh (not necessarily your login Bash) as a non-interactive, non-login shell, which — as covered in How Shell and Bash Work — does not read ~/.bashrc. Confirm the difference directly by dumping both environments and comparing:

env > /home/admin/env-interactive.txt

Add a one-shot cron job that dumps its own environment, wait for it to fire, then compare:

* * * * * /usr/bin/env > /home/admin/env-cron.txt
diff /home/admin/env-interactive.txt /home/admin/env-cron.txt

Typical output shows a much shorter PATH under cron, and the absence of any alias-dependent or ~/.bashrc-exported variable your script silently relied on:

< PATH=/home/admin/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
---
> PATH=/usr/bin:/bin
< APP_MODE=staging

Conclusion: do not fix this by copying ~/.bashrc logic into cron. Fix the script itself: use absolute paths for every command and file it touches, set any required variable explicitly at the top of the script or in the crontab's own environment lines (PATH=... and VAR=value above the schedule line), and do not assume a cron job's shell reads the same startup files your terminal does. Remove the one-shot debug line from the crontab once you have finished comparing.

Practical Scenario: Make a Safer Interactive Listing

Problem: you want a convenient ll command in your own terminal, but you do not want scripts or other users to depend on it.

Create it for the current shell:

alias ll='ls -lah'
type ll
ll ~/lab

Persist it in ~/.bashrc after backup:

cp -a ~/.bashrc ~/.bashrc.before-ll
nano ~/.bashrc
bash -n ~/.bashrc

Open a clean interactive Bash:

bash
type ll
exit

Conclusion: the alias is an interactive convenience. When writing documentation, scripts, or runbooks for other servers, write the full command:

ls -lah /some/path

Common Mistakes

  • Putting spaces around = in assignments.
  • Expecting aliases to work in scripts.
  • Forgetting to quote variable values with spaces.
  • Using $SHELL as proof of the current process.
  • Overwriting PATH instead of prepending or appending.
  • Storing secrets in environment variables without understanding exposure.
  • Editing ~/.bashrc without a backup or syntax check.

Exercises

  1. Create an alias lh='ls -lh', test it with type, then remove it with unalias.
  2. Create a shell variable with a space in the value. Prove that a child Bash cannot see it until you export it.
  3. Temporarily run date with TZ=UTC, then prove your parent shell's TZ did not change.
  4. Add $HOME/.local/bin to PATH in the current shell and verify its position without losing the original PATH.

Verification criterion: you should be able to explain whether a setting affects only the current command line, the current shell, child processes, or future login sessions.

References

  • Bash manual page: https://man7.org/linux/man-pages/man1/bash.1.html
  • GNU Bash Reference Manual: https://www.gnu.org/software/bash/manual/bash.html
  • environ(7) Linux manual page: https://man7.org/linux/man-pages/man7/environ.7.html
  • execve(2) Linux manual page: https://man7.org/linux/man-pages/man2/execve.2.html
  • GNU Coreutils printenv: https://www.gnu.org/software/coreutils/manual/html_node/printenv-invocation.html
  • ld.so(8) Linux manual page (LD_PRELOAD, LD_LIBRARY_PATH): https://man7.org/linux/man-pages/man8/ld.so.8.html
  • crontab(5) Linux manual page: https://man7.org/linux/man-pages/man5/crontab.5.html