Environment Variables, PATH & .bashrc

Linux · Lesson 17 of 32 8 min read Updated

When you type ls, how does Bash know which program to run? When a web app reads APP_ENV=production, where does that value come from? And how do people get shortcuts like ll in their terminal? The answers are environment variables, the PATH, and the shell's start-up files.

In this lesson you will create and export variables, understand and change PATH, and customise Bash with .bashrc, aliases and small functions so that the changes survive a restart.

Shell variables

A variable is a name that holds a value. Create one with name=value, with no spaces around =, and read it with $name:

Terminal
city=Pune
echo "$city"
echo "Orders from ${city}: 3"
Output
Pune
Orders from Pune: 3

The braces in ${city} mark exactly where the name ends, which matters when text follows it directly. Variable names are case-sensitive; by convention, your own variables use lower case and environment variables use UPPER_CASE.

Environment variables and export

A plain variable belongs only to the current shell. Programs you start from that shell are child processes, and they do not see it unless you export it. Exported variables form the environment that every child process inherits.

Terminal
bash -c 'echo "child sees: [$city]"'
export city
bash -c 'echo "child sees: [$city]"'
APP_ENV=production bash -c 'echo "APP_ENV is $APP_ENV"'
echo "after: [$APP_ENV]"
Output
child sees: []
child sees: [Pune]
APP_ENV is production
after: []

bash -c starts a new child shell. Before export, it saw nothing; after, it saw Pune. The third line shows a useful form: NAME=value command sets a variable only for that one command, and the variable is gone afterwards.

Linux sets many environment variables for you. printenv lists them all, and printenv NAME prints one:

VariableHolds
HOMEYour home directory, /home/priya
USERYour user name
SHELLYour login shell, /bin/bash
PWDThe current directory
PATHFolders searched for commands
LANGLanguage and character set, such as en_IN.UTF-8
EDITORYour preferred editor for tools like crontab -e

PATH: how commands are found

When you type a command name without a /, Bash looks for a program with that name in each folder listed in PATH, from left to right, and runs the first one it finds.

Terminal
echo "$PATH"
command -v python3
type -a ls
Output
/home/priya/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
/usr/bin/python3
ls is aliased to `ls --color=auto'
ls is /usr/bin/ls
ls is /bin/ls

Folders are separated by colons. command -v (or which) shows which file would run. type -a shows every match in order; ls appears twice only because /bin is a link to /usr/bin. Because /usr/local/bin comes before /usr/bin, a program you install there by hand wins over the distribution's version with the same name.

Suppose Priya writes a script called greet and keeps it in ~/bin:

Terminal
greet
PATH="$HOME/bin:$PATH"
greet
Output
greet: command not found
Hello from greet, priya

The first try fails because ~/bin is not in PATH. Adding it to the front fixes that for the current shell. To run a program that is not in PATH at all, give its path: ./greet for one in the current folder. The current folder is deliberately not in PATH, so a malicious file named ls in a downloaded folder cannot trick you into running it.

Making changes permanent: start-up files

Everything above disappears when you close the terminal. To keep it, put it in a file that Bash reads when it starts:

FileWhen it is readUse it for
~/.bashrcEvery new interactive shell (each terminal window or tab)Aliases, functions, prompt, EDITOR
~/.profileOnce, at login (SSH login, desktop login)PATH changes and environment variables
/etc/environmentAt login, for every userSystem-wide variables (simple NAME=value lines only)
/etc/profile.d/*.shAt login, for every userSystem-wide scripts from packages or admins

On Ubuntu, ~/.profile already adds ~/bin and ~/.local/bin to PATH when those folders exist, and it also loads ~/.bashrc. After editing a file, either open a new terminal or load it into the current shell with source:

Terminal
nano ~/.bashrc
source ~/.bashrc

source (or its short form .) runs the file in the current shell, so its variables and aliases take effect immediately. Running the file as a normal program would change only a child shell, which then exits.

Aliases and functions

An alias is a short name for a longer command. Ubuntu's default .bashrc already defines a few, such as ll for ls -alF. Add your own at the end of ~/.bashrc:

Terminal
alias ll='ls -alF'
alias update='sudo apt update && sudo apt upgrade'
alias ports='sudo ss -tulpn'
alias rm='rm -I'
mkcd() { mkdir -p "$1" && cd "$1"; }
export EDITOR=nano

The rm='rm -I' alias adds a safety question before large deletes. Aliases cannot take arguments in the middle, so for anything smarter write a function: mkcd projects/new-app creates the folder and moves into it, using $1 for the first argument. You will learn functions properly in Functions, Arguments & Exit Codes. To bypass an alias for one run, put a backslash in front: \rm file.

Advanced: a custom prompt

The prompt is controlled by the variable PS1. Special codes insert information: \u user, \h hostname, \w current directory, \$ ($ or #). Here is a prompt that also shows the time:

Terminal
PS1='[\t] \u@\h:\w\$ '
Output
[13:42:10] priya@laptop:~/projects$

Put the line in ~/.bashrc to keep it. Colour codes can be added too, but keep the prompt short and readable.

Common mistakes

  • Spaces around =. city = Pune runs a command called city. Write city=Pune.
  • Forgetting export. Your script or app cannot see a variable that was never exported.
  • Overwriting PATH. Always append or prepend with :$PATH.
  • Editing .bashrc and expecting the open terminal to change. Run source ~/.bashrc or open a new terminal.
  • Putting secrets in .bashrc. Anything there is easy to leak (screenshots, backups, dotfile repos). Keep secrets in protected files read only by the app.

Key points

  • name=value creates a variable (no spaces); $name or ${name} reads it.
  • export puts a variable into the environment so child processes inherit it; NAME=value cmd sets it for one command.
  • PATH is a colon-separated list of folders searched left to right; command -v shows which program runs.
  • ~/.bashrc runs for every interactive shell and suits aliases and functions; ~/.profile runs at login.
  • source ~/.bashrc applies changes to the current shell immediately.

Exercise

Make these changes permanent for your user: add an alias myip that runs ip -brief address, add a function backup that copies a file to the same name with .bak added, add the folder ~/scripts to the front of PATH, and set EDITOR to nano. Then load the changes and test backup notes/todo.txt.

Show solution

Add these lines to the end of ~/.bashrc:

Terminal
alias myip='ip -brief address'
backup() { cp -v "$1" "$1.bak"; }
export PATH="$HOME/scripts:$PATH"
export EDITOR=nano

Then:

Terminal
source ~/.bashrc
backup notes/todo.txt
Output
'notes/todo.txt' -> 'notes/todo.txt.bak'

The function uses quotes around "$1" so file names with spaces work. Strictly, PATH changes belong in ~/.profile, but putting them in ~/.bashrc is common for a single user; just avoid adding the same folder many times by sourcing repeatedly in one session.

Quick Quiz

1. What does export do?

2. You have two programs called tool, in /usr/local/bin and /usr/bin. Which runs?

3. Which line correctly adds ~/bin to PATH?

4. After adding an alias to ~/.bashrc, how do you use it in the terminal that is already open?

Learn Linux with a mentor

Free lessons get you started. Our Ethical Hacking & Cyber Security training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call