Bash Cheatsheet
Command Substitution
Use this Bash reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Basic Syntax
# Preferred: $() — nestable, readable result=$(command) result=$(ls -la) # Legacy: backticks `` — avoid (hard to nest, harder to read) result=`command` # Both capture stdout; stderr still goes to terminal output=$(cmd 2>&1) # capture both stdout and stderr output=$(cmd 2>/dev/null) # capture stdout, discard stderr
Capturing Command Output
# Assign to variable date_str=$(date '+%Y-%m-%d') user=$(whoami) count=$(ls | wc -l) first_line=$(head -1 file.txt) last_line=$(tail -1 file.txt) # Use inline (don't assign) echo "Today is $(date '+%A, %B %d')" echo "Files: $(ls | wc -l)" mkdir "backup-$(date +%Y%m%d)" # date in filename # In conditions if [[ $(id -u) -eq 0 ]]; then echo "root"; fi if [[ $(whoami) == "deploy" ]]; then echo "deploy user"; fi
Nesting Command Substitutions
# $() nests cleanly echo $(basename $(dirname $(readlink -f "$0"))) # Equivalent with intermediate variables (more readable) script_path=$(readlink -f "$0") script_dir=$(dirname "$script_path") parent=$(basename "$script_dir") echo "$parent" # Deep nesting example latest=$(ls -t $(find . -name "*.log") | head -1)
Arithmetic via Command Substitution
# Use $(( )) for arithmetic (not a command substitution, but similar syntax) result=$(( 2 + 2 )) n=$(( count * 2 )) pi_approx=$(echo "scale=4; 355/113" | bc) # float via bc sqrt=$(echo "sqrt(2)" | bc -l) # expr — old style, avoid in favor of $(( )) result=$(expr 5 + 3) # 8 result=$(expr "$a" \* "$b") # multiply (must escape *)
Common Patterns
# Get script's own directory SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # Get absolute path abs=$(realpath "$file") abs=$(cd "$(dirname "$file")" && pwd)/$(basename "$file") # Count lines/words/chars lines=$(wc -l < file.txt) # note < avoids filename in output words=$(wc -w < file.txt) chars=$(wc -c < file.txt) # Trim trailing newline ($(…) does this automatically) content=$(cat file.txt) # trailing newlines stripped # Split output into array mapfile -t arr < <(find . -name "*.py") files=( $(glob_pattern) ) # simple, breaks on spaces # Get PID of current shell (not $$, which works but avoid in subshells) mypid=$(exec sh -c 'echo "$PPID"')
Whitespace and Trailing Newlines
# Command substitution strips trailing newlines automatically str=$(printf "hello\n\n\n") echo "${#str}" # 5, not 8 — trailing \n stripped # To preserve trailing newlines, append a sentinel character str=$(printf "line1\nline2\n"; echo x) str="${str%x}" # remove the sentinel # Word splitting on unquoted substitution files=$(ls) echo $files # word-split and glob-expanded (dangerous!) echo "$files" # safe: single string with newlines # Loop over lines (right way) while IFS= read -r line; do echo "$line" done < <(find . -name "*.txt")
Process Substitution vs Command Substitution
# Command substitution: captures output as a string content=$(cat file.txt) # Process substitution: provides a filename-like path to command output diff <(sort a.txt) <(sort b.txt) # <() is a file, not a string # Use process substitution when a command needs a filename argument # Use command substitution when you need the output as a value # Feed process substitution to while loop (avoids subshell, vars persist) count=0 while IFS= read -r line; do (( count++ )) done < <(grep "pattern" bigfile.txt) echo "Matches: $count" # works — no subshell
Subshell Behavior
# $() creates a subshell — variable assignments don't persist result=$(x=10; echo $x) echo $x # empty — x was set only in the subshell # To persist changes, use eval (carefully) or restructure read x <<< "$(some_command)" # one way to extract a value # The subshell inherits the parent's environment (read-only copy) VAR="parent" child=$(echo $VAR) # VAR is visible echo "$child" # "parent"
Capturing Exit Status
# The exit status of command substitution is the exit status of the command output=$(grep "pattern" file.txt) status=$? echo "Exit: $status" # Combine capture and status check if output=$(some_command); then echo "Success: $output" else echo "Failed with status $?" fi # With set -e, a failed command substitution exits the script set -e content=$(false_command) # script exits here if false_command fails # Use || to handle it: content=$(false_command) || { echo "failed"; exit 1; }
Useful Command Substitution Idioms
# Current timestamp ts=$(date '+%Y%m%d_%H%M%S') # Random hex string rand=$(openssl rand -hex 16) # Git info branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) commit=$(git rev-parse --short HEAD 2>/dev/null) # System info os=$(uname -s) arch=$(uname -m) cores=$(nproc) # Linux cores=$(sysctl -n hw.ncpu) # macOS # File content (faster than cat in some shells) content=$(<file.txt) # Bash built-in, no external process # Check if inside a git repo git_root=$(git rev-parse --show-toplevel 2>/dev/null) && echo "in git repo" # Get the real username even when sudo'd real_user=$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}")