Master Linux environment variables and shell profiles. This comprehensive guide covers everything from basic variable management to advanced profile configuration with practical examples and best practices.
What are Environment Variables?
Environment variables are dynamic named values that affect how processes behave on a computer. They are essential for configuring applications and the shell environment.
- Configuration storage: Store paths, settings, and preferences
- Process communication: Pass information between processes
- Shell customization: Define aliases, functions, and prompts
- Path management: Control where executables are found
- Security: Store API keys and sensitive data (with caution)
- Cross-platform compatibility: Standardized across Unix-like systems
1. Profile Files Explained
Profile Loading Order
# Login Shell (ssh, console login, sudo -i, su -)
/etc/profile
└── /etc/profile.d/*.sh
~/.bash_profile (if exists)
OR ~/.bash_login (if .bash_profile doesn't exist)
OR ~/.profile (if neither exists)
# Interactive Non-Login Shell (terminal, screen, tmux)
/etc/bash.bashrc (or /etc/bashrc)
~/.bashrc
# Non-Interactive Shell (scripts, cron jobs)
BASH_ENV variable (if set)
OR no profile files loaded
# Shell Exit
~/.bash_logout (for login shells)
2. Environment Variable Management
3. Common Environment Variables
Essential System Variables
PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOME/home/usernameUSERSHELL/bin/bashPWDOLDPWDPS1\u@\h:\w\$PS2>LANGen_US.UTF-8TERMxterm-256color4. Profile File Examples
Complete .bashrc Example
# ~/.bashrc - executed for interactive non-login shells
# If not running interactively, don't do anything
case $- in
*i*) ;;
*) return;;
esac
# Don't put duplicate lines or lines starting with space in history
HISTCONTROL=ignoreboth
# Append to the history file, don't overwrite it
shopt -s histappend
# History length (see HISTSIZE and HISTFILESIZE)
HISTSIZE=10000
HISTFILESIZE=20000
# Check window size after each command
shopt -s checkwinsize
# Make less more friendly for non-text input files
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
# Set variable identifying the chroot you work in
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
# Colored prompt
force_color_prompt=yes
if [ -n "$force_color_prompt" ]; then
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
color_prompt=yes
else
color_prompt=
fi
fi
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt
# If this is an xterm set title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
;;
*)
;;
esac
# Enable color support for ls and grep
if [ -x /usr/bin/dircolors ]; then
test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
alias ls='ls --color=auto'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
fi
# Colored GCC warnings and errors
export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
# Aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
# Enable programmable completion features
if ! shopt -oq posix; then
if [ -f /usr/share/bash-completion/bash_completion ]; then
. /usr/share/bash-completion/bash_completion
elif [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi
fi
# Custom environment variables
export EDITOR='vim'
export VISUAL='vim'
export PAGER='less'
export BROWSER='firefox'
# Custom PATH additions
export PATH="$HOME/bin:$PATH"
export PATH="$HOME/.local/bin:$PATH"
export PATH="/usr/local/go/bin:$PATH"
# Java configuration
export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
export PATH="$JAVA_HOME/bin:$PATH"
# Python configuration
export PYTHONPATH="$HOME/python:$PYTHONPATH"
export PYTHONSTARTUP="$HOME/.pythonrc"
# Node.js configuration
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
# Docker configuration
export DOCKER_HOST="unix:///var/run/docker.sock"
# Custom functions
function mcd() { mkdir -p "$1" && cd "$1"; }
function cl() { cd "$1" && ls; }
function grep() { command grep --color=auto "$@"; }
function ipinfo() { curl ipinfo.io/"$1"; }
# Git prompt support
if [ -f ~/.git-prompt.sh ]; then
source ~/.git-prompt.sh
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[01;31m\]$(__git_ps1)\[\033[00m\]\$ '
fi
# Load local configuration if exists
if [ -f ~/.bashrc_local ]; then
source ~/.bashrc_local
fi
Complete .profile Example
# ~/.profile - executed by Bourne-compatible login shells
# If running bash
if [ -n "$BASH_VERSION" ]; then
# Include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
# Set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi
# Set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
PATH="$HOME/.local/bin:$PATH"
fi
# Set default editor
export EDITOR=vim
export VISUAL=vim
# Set language
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
# Increase file descriptor limits
ulimit -n 4096
# Set umask for file creation
umask 022
# Set history settings
export HISTSIZE=10000
export HISTFILESIZE=20000
export HISTCONTROL=ignoreboth:erasedups
export HISTIGNORE="ls:ll:la:cd:exit:history"
# Set less options
export LESS='-R -i -j5 -M -W -z-4 -g -e -X -F'
# Set man page colors
export LESS_TERMCAP_mb=$'\e[1;32m'
export LESS_TERMCAP_md=$'\e[1;32m'
export LESS_TERMCAP_me=$'\e[0m'
export LESS_TERMCAP_se=$'\e[0m'
export LESS_TERMCAP_so=$'\e[01;33m'
export LESS_TERMCAP_ue=$'\e[0m'
export LESS_TERMCAP_us=$'\e[1;4;31m'
# Set grep colors
export GREP_COLORS='mt=01;31:sl=:cx=:fn=35:ln=32:bn=32:se=36'
# Custom environment variables for applications
export GOPATH="$HOME/go"
export PATH="$GOPATH/bin:$PATH"
export NVM_DIR="$HOME/.nvm"
export NODE_ENV="development"
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1
export AWS_CLI_AUTO_PROMPT="on-partial"
# Load any machine-specific configuration
if [ -f "$HOME/.profile_local" ]; then
. "$HOME/.profile_local"
fi
5. Advanced Configuration Techniques
Conditional Profile Configuration
# Conditionally load configurations based on:
# 1. Operating system
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux-specific settings
alias open='xdg-open'
export BROWSER='firefox'
elif [[ "$OSTYPE" == "darwin"* ]]; then
# macOS-specific settings
export BROWSER='open'
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
fi
# 2. Shell type
if [[ "$SHELL" == "/bin/bash" ]]; then
# Bash-specific settings
shopt -s globstar # Enable ** pattern
shopt -s direxpand
elif [[ "$SHELL" == "/bin/zsh" ]]; then
# Zsh-specific settings
setopt extendedglob
autoload -Uz compinit && compinit
fi
# 3. Hostname
case "$HOSTNAME" in
production-*)
export ENVIRONMENT="production"
export PS1="\[\e[31m\]\u@\h\[\e[0m\]:\w\$ "
;;
staging-*)
export ENVIRONMENT="staging"
export PS1="\[\e[33m\]\u@\h\[\e[0m\]:\w\$ "
;;
*)
export ENVIRONMENT="development"
export PS1="\[\e[32m\]\u@\h\[\e[0m\]:\w\$ "
;;
esac
# 4. Terminal capabilities
if [[ "$TERM" == "xterm-256color" ]] || [[ "$TERM" == "screen-256color" ]]; then
# Enable 256-color support
export TERM=xterm-256color
alias ls='ls --color=auto'
elif [[ "$TERM" == "xterm" ]]; then
# Basic color support
alias ls='ls --color=auto'
fi
# 5. SSH connection
if [[ -n "$SSH_CONNECTION" ]]; then
export PS1="[\u@\h \W]\\$ "
# Disable SSH agent forwarding for security
export SSH_AUTH_SOCK=""
fi
# 6. Docker container detection
if grep -q docker /proc/1/cgroup 2>/dev/null; then
export IN_DOCKER="true"
export PS1="\[\e[36m\](docker)\[\e[0m\]$PS1"
fi
Profile Organization Strategies
~/.bashrc.d/
├── 10-aliases.sh # Aliases (loaded first)
├── 20-functions.sh # Shell functions
├── 30-environment.sh # Environment variables
├── 40-completion.sh # Completion scripts
├── 50-apps.sh # Application-specific config
└── 99-local.sh # Local overrides (loaded last)
# In ~/.bashrc
for file in ~/.bashrc.d/*.sh; do
[[ -r "$file" ]] && source "$file"
done
# ~/.bash_hosts/ - Host-specific configurations
~/.bash_hosts/
├── host1.example.com.sh
├── host2.example.com.sh
└── default.sh
# In ~/.bashrc
HOSTFILE="$HOME/.bash_hosts/$(hostname -f).sh"
if [[ -f "$HOSTFILE" ]]; then
source "$HOSTFILE"
elif [[ -f "$HOME/.bash_hosts/default.sh" ]]; then
source "$HOME/.bash_hosts/default.sh"
fi
# Environment-based configuration
case "$ENVIRONMENT" in
production)
export LOG_LEVEL="WARN"
export DEBUG="false"
;;
staging)
export LOG_LEVEL="INFO"
export DEBUG="true"
;;
development)
export LOG_LEVEL="DEBUG"
export DEBUG="true"
export PYTHONPATH="$HOME/dev:$PYTHONPATH"
;;
esac
6. System-wide Configuration
/etc/environment Example
# /etc/environment - System-wide environment variables
# This file is NOT a shell script but a simple key=value file
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
LANG="en_US.UTF-8"
LC_ALL="en_US.UTF-8"
TZ="UTC"
EDITOR="vim"
PAGER="less"
MANPAGER="less -R"
# Application-specific variables
JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
GOPATH="/opt/go"
NODE_PATH="/usr/lib/node_modules"
# Proxy settings (if needed)
# http_proxy="http://proxy.example.com:8080/"
# https_proxy="http://proxy.example.com:8080/"
# ftp_proxy="http://proxy.example.com:8080/"
# no_proxy="localhost,127.0.0.1,.example.com"
# Security settings
UMASK="022"
HISTCONTROL="ignoreboth"
HISTSIZE="1000"
HISTFILESIZE="2000"
/etc/profile.d/ Directory
7. Best Practices & Common Pitfalls
1. Circular sourcing: Avoid sourcing .bashrc from .profile and vice versa
2. Long PATH variable: Keep PATH manageable (50-100 entries max)
3. Unquoted variables: Always quote variables:
"$VAR" not $VAR4. Sensitive data in profiles: Never store passwords/API keys in plain text
5. Overwriting system variables: Append/prepend instead:
PATH="$PATH:new"6. Ignoring exit codes: Check command success:
command || echo "Failed"7. Hardcoded paths: Use
$HOME instead of /home/username8. Too many aliases: Keep aliases intuitive and minimal
9. Forgetting to export: Variables need
export to be available to subprocesses10. Not testing changes: Test with
bash --noprofile --norc first
Security Best Practices
# 1. Secure sensitive data
# Store secrets in ~/.env file (not in profiles)
if [[ -f "$HOME/.env" ]]; then
# Use restrictive permissions: chmod 600 ~/.env
source "$HOME/.env"
fi
# 2. Validate environment variables
if [[ -z "$REQUIRED_VAR" ]]; then
echo "ERROR: REQUIRED_VAR is not set" >&2
exit 1
fi
# 3. Sanitize input
SAFE_INPUT="${USER_INPUT//[^a-zA-Z0-9_-]/}"
# 4. Use readonly for constants
readonly API_BASE_URL="https://api.example.com"
readonly MAX_RETRIES=3
# 5. Secure PATH
# Remove current directory from PATH
export PATH="${PATH//:\./}" # Remove standalone .
export PATH="${PATH//:\.\//}" # Remove ./
# 6. Limit permissions
umask 077 # Restrictive permissions for new files
# 7. Audit environment
function audit_env() {
echo "=== Environment Audit ==="
echo "UID: $UID"
echo "USER: $USER"
echo "HOME: $HOME"
echo "PATH: $PATH"
echo "SHELL: $SHELL"
echo "========================="
}
# 8. Clean environment for sensitive operations
function clean_env() {
env -i /bin/bash --noprofile --norc
}
8. Troubleshooting & Debugging
Troubleshooting Checklist
- Test with clean shell:
bash --noprofile --norc - Check file permissions:
ls -la ~/.bashrc - Verify syntax:
bash -n ~/.bashrc - Trace execution:
bash -x ~/.bashrc 2>&1 | head -50 - Check for circular sourcing
- Validate PATH ordering and duplicates
- Test login vs non-login shell behavior
- Check for missing dependencies
- Verify variable expansion works correctly
- Test on different terminals (xterm, screen, tmux)
Master Environment Variables & Shell Profiles
Environment variables and shell profiles are fundamental to Linux system administration and development. By understanding how they work, where they're stored, and how they interact, you can create efficient, maintainable, and secure shell environments.
Remember: Keep your profiles organized, modular, and well-documented. Use appropriate scoping for variables, always secure sensitive data, and regularly audit your configurations. A well-configured shell environment significantly boosts productivity and reduces errors.
Next Steps: Start by auditing your current environment with env | sort. Organize your existing profiles into modular components. Implement environment-specific configurations. Set up secure handling for sensitive data. As you refine your profiles, you'll develop a shell environment that works perfectly for your workflow while remaining clean and maintainable.