The original Unix glob behavior, you must explicitly use .* or enable dotglob with shopt -s dotglob.
Extended globbing provides pattern matching similar to regex quantifiers:
Pattern Meaning Regex equivalent ?(pattern)Match zero or one occurrence pattern?*(pattern)Match zero or more occurrences pattern*+(pattern)Match one or more occurrences pattern+@(pattern1|pattern2)Match exactly one of the patterns (p1|p2)!(pattern)Match anything except the pattern Negative lookahead
# Remove all files except .gitkeep
# Match files with exactly one digit in the name
Job control allows you to manage multiple processes within a single shell session. The shell tracks Jobs (pipeline groups) and assigns them to the shell’s session.
# Suspend the current foreground job
# Resume job 1 in the background
# Bring job 1 to the foreground
# Suspend a job after it starts
nohup command & # immune to SIGHUP on shell exit
# Disown a job (removes from shell's job table)
# Run a job in the background
Every job is a process group with a unique PGID. The terminal assigns one process group as the foreground process group , only this group receives terminal input (stdin) and signals (SIGINT, SIGQUIT, SIGTSTP).
ps -eo pid,pgid,sid,comm | head
# The shell's own process group
echo " Shell PID: $$ , PGID: $( ps -o pgid= -p $$ ) "
A[Session Leader - SSH/bash] --> B[Foreground Process Group]
A --> C[Background Process Group 1]
A --> D[Background Process Group 2]
B --> E[Pipeline: cmd1 \| cmd2 \| cmd3]
When a shell session ends (SSH disconnect, terminal close), it sends SIGHUP to all jobs in the Session. Two mechanisms prevent this:
# Method 1: nohup, ignores SIGHUP, redirects output to nohup.out
# Method 2: disown, removes job from shell's active table
disown -h %1 # prevents SIGHUP on shell exit
The difference: nohup modifies the signal handling of the child process itself, while disown tells the shell not to send SIGHUP when it exits. If the shell crashes (SIGKILL), nohup-protected processes survive, but disown-ed processes may still receive SIGHUP depending on the terminal driver behavior.
# Shell variable, visible only to current shell
# Export to environment, visible to child processes
declare -x MY_VAR = " hello "
# Local variable (function-scoped in bash)
# List all environment variables
# List all shell variables (including unset ones)
# Check if a variable is set
if [[ -v MY_VAR ]]; then echo " set " ; fi
Variable Meaning $?Exit status of the last command (0 = success) $$PID of the current shell $!PID of the last background process $-Current shell options $_Last argument of the previous command $@All positional parameters as separate words $*All positional parameters as a single word $#Number of positional parameters $0Name of the shell or script $1..$9Positional parameters ${10}Positional parameter 10+ (must use braces) IFSInternal Field Separator (default: space, tab, newline) PATHColon-separated list of directories to search for commands
# Default value if unset or null
echo " ${ VAR :- default } " # does not assign
echo " ${ VAR := default } " # assigns to VAR
# Alternative value if set and non-null
echo " ${ VAR : + alternative } "
: " ${ VAR :? VAR must be set } "
echo " ${ VAR : 2 : 5 } " # 5 characters starting at offset 2
# Remove prefix (shortest match)
# Remove prefix (longest match)
# Remove suffix (shortest match)
# Remove suffix (longest match)
# Replace first occurrence
echo " ${ VAR / pattern / replacement } "
# Replace all occurrences
echo " ${ VAR // pattern / replacement } "
echo " ${ VAR /# pattern / replacement } "
echo " ${ VAR /% pattern / replacement } "
# Convert to uppercase (bash 4.0+)
# Convert to lowercase (bash 4.0+)
File Sourced by Purpose /etc/environmentPAM System-wide environment variables /etc/profileLogin shells System-wide login configuration /etc/bash.bashrcInteractive shells System-wide interactive config ~/.profileLogin shells User login configuration ~/.bashrcInteractive non-login shells User interactive configuration ~/.bash_profileLogin shells (bash) Alternative to .profile ~/.bash_loginLogin shells (bash, fallback) Fallback if .bash_profile missing /etc/profile.d/*/etc/profileDrop-in system scripts
The shebang (#!) line tells the kernel which interpreter to use when executing the script as a Direct command (./script.sh):
#!/usr/bin/env bash # portable, finds bash in PATH
The env form is preferred for languages that may be installed in different locations (/usr/bin/bash, /usr/local/bin/bash, /opt/homebrew/bin/bash). The kernel only allows one Argument on the shebang line, so #!/usr/bin/bash -e works but #!/usr/bin/env bash -e does not, bash -e is treated as a single argument to env.
Quoting controls word splitting and globbing. The rules are:
Quote Type Expansions Performed No quotes All expansions, word splitting, globbing Double "" Parameter expansion, command substitution, arithmetic Single '' None, literal string
# Without quotes, word splitting on IFS
echo $VAR # outputs: hello world (but splits into two args)
# With double quotes, no word splitting
echo " $VAR " # outputs: hello world (single arg)
# Single quotes, no expansion at all
echo ' $VAR ' # outputs: $VAR
# Escaping within double quotes
echo " cost: \"\$ 5 \" " # outputs: cost: "$5"
echo ' it ''' s ' # outputs: it ' s (end single, escaped single, start single )
if [ -f /etc/passwd ]; then
# Bash test (recommended in bash scripts)
if [[ -f /etc/passwd ]]; then
# Pattern matching in [[ ]]
if [[ " $FILE " == * .conf ]]; then
echo " Configuration file "
# Regex matching in [[ ]]
if [[ " $STRING " =~ ^[0-9]+$ ]]; then
echo " Too many arguments "
[ -f file ] # exists and is regular file
[ -d dir ] # exists and is directory
[ -s file ] # exists and has size > 0
[ -L link ] # symbolic link
[ file1 -nt file2 ] # file1 is newer than file2
[ file1 -ot file2 ] # file1 is older than file2
# C-style for loop (bash)
for (( i = 0 ; i < 10 ; i ++ )); do
until ping -c 1 -W 1 host.example.com & > /dev/null ; do
echo " Waiting for host... "
# Select loop (interactive menu)
select option in " Start " " Stop " " Restart " " Quit " ; do
Start ) systemctl start service ;;
Stop ) systemctl stop service ;;
Restart ) systemctl restart service ;;
local env = " ${1 :- production } "
local version = " ${2 :- latest } "
echo " Deploying version $version to $env "
if [[ " $env " == " production " ]]; then
echo " Running pre-deploy checks... "
# Function with local variables (bash)
# -e: Exit immediately if any command fails (ERR trap)
# -u: Treat unset variables as errors
# -o pipefail: Pipeline fails if ANY command in it fails
# (without this, only the last command's exit status matters)