Everything you need to master GNU Bash โ from basic commands to advanced scripting, process management, text processing, networking, and system administration.
Bash (**B**ourne **A**gain **Sh**ell) is a Unix shell and command language written by Brian Fox for the GNU Project. It is the default shell on most Linux distributions and macOS (prior to Catalina).
# Make executable and runchmod+xscript.sh
./script.sh
# Run with bash explicitlybashscript.sh
# Run in current shell (source)sourcescript.sh
.script.sh
# Run with debug outputbash-xscript.sh
# Syntax check without executingbash-nscript.sh
# Sequential executioncommand1;command2
# AND โ run command2 only if command1 succeedscommand1&&command2
# OR โ run command2 only if command1 failscommand1||command2
# Background executioncommand1&# Pipe stdout of command1 to stdin of command2command1|command2
# Pipe both stdout and stderrcommand1|&command2
# Negate exit status!command1
# Successexit0# General errorexit1# Misuse of shell built-inexit2# Command not found (by convention)exit127# Killed by signal Nexit$((128+N))# Check last exit codeecho$?# Exit code of last background commandecho$!
# Simple assignment (no spaces around =)name="John Doe"age=30readonlyPI=3.14159
# From command outputcurrent_date=$(date+%Y-%m-%d)file_count=$(ls|wc-l)# From arithmeticresult=$((5+3))# Assign with default:"${VAR:=default_value}"# Multiple assignmentsa=1b=2c=3
# Integerdeclare-inum=42num=num+8# Arithmetic without $(())echo$num# => 50# Read-onlydeclare-rCONST="immutable"# Arraydeclare-alist=(onetwothree)# Associative array (dictionary)declare-Amap=([key1]=val1[key2]=val2)# Export (environment variable)declare-xGLOBAL_VAR="visible to child processes"# Lowercasedeclare-llower="HELLO"echo$lower# => hello# Uppercasedeclare-uupper="hello"echo$upper# => HELLO# Name reference (pointer to another variable)declare-nref=name
echo$ref# => John Doe# Print all attributes of a variabledeclare-pname
# Function tracedeclare-ftmyfunc
# Print all functionsdeclare-F
# Print all function definitionsdeclare-f
# Set for current sessionexportMY_VAR="value"# Set for a single commandMY_VAR="value"command# UnsetunsetMY_VAR
# List all environment variablesenv
printenv
# List specific variableprintenvHOME
# Persist across sessions (add to ~/.bashrc or ~/.bash_profile)echo'export MY_VAR="value"'>>~/.bashrc
source~/.bashrc
# Use default if unset or nullecho"${var:-default}"# $var or "default"# Assign default if unset or nullecho"${var:=default}"# Sets var=default if unset# Use alternative if set and non-nullecho"${var:+alternative}"# "alternative" if var is set# Error if unset or nullecho"${var:?'var is required'}"# Without colon โ only checks if unset (null is OK)echo"${var-default}"# $var or "default" if unsetecho"${var=default}"echo"${var+alternative}"echo"${var?'error message'}"
path="/usr/local/bin/script.sh"# Remove shortest match from frontecho"${path#*/}"# => usr/local/bin/script.sh# Remove longest match from frontecho"${path##*/}"# => script.sh (basename)# Remove shortest match from endecho"${path%/*}"# => /usr/local/bin (dirname)# Remove longest match from endecho"${path%%/*}"# => (empty โ everything after first /)# Practical examplesfile="archive.tar.gz"echo"${file%.*}"# => archive.tar (remove ext)echo"${file%%.*}"# => archive (remove all ext)echo"${file#*.}"# => tar.gz (remove name)echo"${file##*.}"# => gz (get extension)
str="Hello World World"# Replace first occurrenceecho"${str/World/Earth}"# => Hello Earth World# Replace all occurrencesecho"${str//World/Earth}"# => Hello Earth Earth# Replace at beginning (prefix)echo"${str/#Hello/Hi}"# => Hi World World# Replace at end (suffix)echo"${str/%World/Earth}"# => Hello World Earth# Delete (replace with nothing)echo"${str// /}"# => HelloWorldWorld
var_name="greeting"greeting="Hello!"# Indirect referenceecho"${!var_name}"# => Hello! (value of $greeting)# List matching variable namesecho"${!BASH*}"# All vars starting with BASHecho"${!HOME*}"# All vars starting with HOME
arr=("one""two""three""four""five")# Access elementsecho"${arr[0]}"# => one (first)echo"${arr[-1]}"# => five (last)echo"${arr[@]}"# All elementsecho"${arr[*]}"# All as single string# Lengthecho"${#arr[@]}"# => 5 (number of elements)echo"${#arr[0]}"# => 3 (length of first element)# Sliceecho"${arr[@]:1:3}"# => two three four (offset:length)echo"${arr[@]:2}"# => three four five (from offset)# Keys/indicesecho"${!arr[@]}"# => 0 1 2 3 4# Appendarr+=("six")arr=("${arr[@]}""seven")# Prependarr=("zero""${arr[@]}")# Remove elementunset'arr[2]'# Re-index after removalarr=("${arr[@]}")# Copyarr_copy=("${arr[@]}")# Mergemerged=("${arr1[@]}""${arr2[@]}")# Filter by patternmatches=("${arr[@]/t*/}")# Remove matching elements# Check if element existsif[[" ${arr[*]} "==*" two "*]];thenecho"Found"fi# Sort arrayIFS=$'\n'sorted=($(sort<<<"${arr[*]}"));unsetIFS
# Unique valuesIFS=$'\n'unique=($(sort-u<<<"${arr[*]}"));unsetIFS
# Reverse arrayfor((i=${#arr[@]}-1;i>=0;i--));doreversed+=("${arr[i]}")done# Join with delimiterfunctionjoin_by{locald=${1-}f=${2-}ifshift2;thenprintf'%s'"$f""${@/#/$d}"fi}join_by','"${arr[@]}"# => one,two,three,four,five
# By valueforitemin"${arr[@]}";doecho"$item"done# By indexforiin"${!arr[@]}";doecho"$i: ${arr[$i]}"done# While loop with indexi=0while[$i-lt${#arr[@]}];doecho"${arr[$i]}"((i++))done
# Declarationdeclare-Auser
user[name]="John"user[age]=30user[email]="john@example.com"# Or inlinedeclare-Aconfig=([host]="localhost"[port]="8080"[debug]="true")# Accessecho"${config[host]}"# => localhost# All valuesecho"${config[@]}"# All keysecho"${!config[@]}"# Number of elementsecho"${#config[@]}"# Check key existsif[[-vconfig[host]]];thenecho"Key exists"fi# Delete keyunset'config[debug]'# Iterateforkeyin"${!config[@]}";doecho"$key = ${config[$key]}"done
# Read file into array (one line per element)mapfile-tlines<file.txt
# Read with callback every N linesmapfile-t-c5-C'process_batch'lines<file.txt
# Read specific number of linesmapfile-t-n10lines<file.txt
# Skip first N linesmapfile-t-s3lines<file.txt
# From command outputmapfile-tresults<<(find.-name"*.sh")# Read into array starting at indexmapfile-t-O5lines<file.txt
case"$variable"inpattern1)commands
;;pattern2|pattern3)commands
;;[0-9]*)echo"Starts with digit";;*.txt)echo"Text file";;*)echo"Default case";;esac# Fall-through (Bash 4+)case"$var"inpattern1)commands
;;&# Continue testing patternspattern2)commands
;&# Fall through to next casepattern3)commands
;;esac
# Break out of loopforiin{1..10};do[[$i-eq5]]&&breakecho"$i"done# Break out of nested loop (level N)foriin{1..3};doforjin{1..3};do[[$j-eq2]]&&break2# Break both loopsdonedone# Skip iterationforiin{1..10};do[[$((i%2))-eq0]]&&continueecho"$i"# Odd numbers onlydone# Continue in nested loopforiin{1..3};doforjin{1..3};do[[$j-eq2]]&&continue2# Skip to next idonedone
add(){locala=$1b=$2echo$((a+b))}# Capture outputresult=$(add35)echo"$result"# => 8# Return status codeis_even(){return$(($1%2))}ifis_even4;thenecho"Even"fi# Return via global variabledivide(){if[[$2-eq0]];thenecho"Error: division by zero">&2return1fiRESULT=$(echo"scale=4; $1/$2"|bc)}
outer(){localx=10inner(){localy=20echo"inner: x=$x, y=$y"# x is visible}inner
echo"outer: x=$x"# y is NOT visible}# Dynamic scopingfunc_a(){localvar="from A"func_b
}func_b(){echo"$var"# => "from A" (dynamic scoping)}
# Return values through referencefill_array(){local-narr_ref=$1arr_ref=(onetwothree)}declare-amy_array
fill_arraymy_array
echo"${my_array[@]}"# => one two three
# Open file for reading on FD 3exec3<file.txt
read-rline<&3exec3<&-# Close FD 3# Open file for writing on FD 4exec4>output.txt
echo"data">&4exec4>&-# Close FD 4# Open file for read/write on FD 5exec5<>file.txt
read-rline<&5echo"new data">&5exec5>&-
# Duplicate FDsexec3>&1# FD 3 = copy of stdoutexec1>file.txt# Redirect stdout to fileecho"goes to file"exec1>&3# Restore stdoutexec3>&-# Close FD 3# Swap stdout and stderrcommand3>&11>&22>&33>&-
# Basic readread-ranswer
echo"You said: $answer"# With promptread-rp"Enter name: "name
# Silent input (passwords)read-rsp"Password: "pass;echo# Read with timeoutread-rt5-p"Quick! "answer
# Read N charactersread-rn1-p"Press any key..."# Read into arrayread-raarr<<<"one two three"# Read with custom delimiterread-rd''content<file.txt# Read entire file# Read specific number of charsread-rN10chunk<file.txt
# Default variable ($REPLY)read-rp"Input: "echo"$REPLY"
# Treat command output as a filediff<(lsdir1)<(lsdir2)# Treat input as a filecommand>>(teelog.txt)# Compare sorted files without temp filesdiff<(sortfile1.txt)<(sortfile2.txt)# Feed multiple commands' outputpaste<(cut-f1data.txt)<(cut-f3data.txt)# Write to multiple commandsecho"data"|tee>(cmd1)>(cmd2)>/dev/null
# While loop without subshellwhileIFS=read-rline;do# Variables set here survive the loopcount=$((count+1))done<<(some_command)echo"$count"
# Basic usagefind.-name"*.txt"|xargsrm
# With placeholderfind.-name"*.log"|xargs-I{}mv{}{}.bak
# Parallel executionfind.-name"*.jpg"|xargs-P4-I{}convert{}{}.png
# Handle spaces in filenamesfind.-name"*.txt"-print0|xargs-0rm
# Limit arguments per commandecho{1..100}|xargs-n10# Prompt before executionfind.-name"*.tmp"|xargs-prm
# With custom delimiterecho"a:b:c:d"|xargs-d:echo# From filexargs-afile_list.txtrm
*# Match any string (including empty)?# Match any single character[abc]# Match any character in set[a-z]# Match any character in range[!abc]# Match any character NOT in set[^abc]# Same as above (alternate syntax)
shopt-sextglob
?(pattern)# Zero or one occurrence*(pattern)# Zero or more occurrences+(pattern)# One or more occurrences@(pattern)# Exactly one occurrence!(pattern)# Anything except the pattern# Examplesls*.@(jpg|png|gif)# All image filesrm!(*.txt)# Remove all except .txt filesls?(a)b# b or abls*(.[0-9])# Files like .1, .2, .12ls+([0-9])# One or more digits
shopt-snullglob# Unmatched globs expand to nothingshopt-sfailglob# Unmatched globs cause errorshopt-sdotglob# Include hidden files in globsshopt-snocaseglob# Case-insensitive globsshopt-sglobstar# ** matches directories recursivelyshopt-sextglob# Enable extended pattern matching# Recursive glob (Bash 4+)shopt-sglobstar
ls**/*.txt# All .txt files recursively# Example with nullglobshopt-snullglob
files=(*.xyz)echo"${#files[@]}"# 0 if no match (instead of literal *.xyz)
# Using [[ =~ ]]if[["Hello123"=~^[A-Za-z]+[0-9]+$]];thenecho"Match!"fi# Capture groupsif[["2026-02-20"=~^([0-9]{4})-([0-9]{2})-([0-9]{2})$]];thenecho"Year: ${BASH_REMATCH[0]}"# Full matchecho"Year: ${BASH_REMATCH[1]}"# 2026echo"Month: ${BASH_REMATCH[2]}"# 02echo"Day: ${BASH_REMATCH[3]}"# 20fi# Store regex in variable (avoid quoting issues)re='^[0-9]+$'if[["$input"=~$re]];thenecho"Is a number"fi
history# Show all historyhistory20# Show last 20 commandshistory-c# Clear historyhistory-d42# Delete entry 42history-a# Append session history to filehistory-r# Read history filehistory-w# Write history to file
!!# Last command!n# Command number n!-n# n-th most recent command!string# Most recent starting with string!?string?# Most recent containing string^old^new# Repeat last, replacing old with new^old^new^:G# Replace all occurrences
!!:0# Command name of last command!!:1# First argument!!:$# Last argument (same as !$)!!:^# First argument (same as !^)!!:n# n-th argument!!:n-m# Arguments n through m!!:n-$# Arguments n through last!!:*# All arguments (same as !*)
!!:h# Remove trailing filename (head/dirname)!!:t# Remove leading path (tail/basename)!!:r# Remove trailing suffix (.ext)!!:e# Remove all but suffix!!:p# Print without executing!!:s/old/new/# Substitute first!!:gs/old/new/# Substitute all (global)!!:q# Quote the expansion!!:x# Quote, breaking into words
# Run previous command with sudosudo!!
# Use last argument of previous commandcd!$
mkdir!$&&cd!$
# Fix typo in last command^typo^fixed
# Use all arguments from previous commandecho!*
# Show what would execute without running!42:p
# In ~/.bashrcexportHISTSIZE=10000# Commands in memoryexportHISTFILESIZE=20000# Commands in fileexportHISTCONTROL=ignoreboth# Ignore dupes & leading spacesexportHISTIGNORE="ls:cd:exit"# Don't record theseexportHISTTIMEFORMAT="%F %T "# Timestamp formatshopt-shistappend# Append, don't overwriteshopt-shistverify# Show before executingPROMPT_COMMAND='history -a'# Save after each command
# Run in backgroundcommand&# List jobsjobsjobs-l# With PIDsjobs-r# Running onlyjobs-s# Stopped only# Bring to foregroundfg# Most recent jobfg%1# Job number 1fg%string# Job whose name starts with string# Send to backgroundbg# Most recent jobbg%1# Job number 1# Suspend foreground process# Ctrl+Z# Wait for background jobswait# Wait for allwait%1# Wait for job 1wait$pid# Wait for specific PID# Disown (prevent SIGHUP on logout)disown# Most recent jobdisown%1# Job 1disown-a# All jobsdisown-h%1# Don't kill on logout but keep in job table
# View processesps# Current terminalpsaux# All processes (BSD style)ps-ef# All processes (System V style)ps-ejH# Process treepsaxopid,ppid,cmd# Custom columns# Real-time process viewertop
htop# Interactive (if installed)# Find process by namepgrepprocess_name
pgrep-uroot# By userpgrep-f"pattern"# Match full command linepidofprocess_name
# Kill processeskillPID# Send SIGTERMkill-9PID# Send SIGKILL (force)kill-STOPPID# Pause processkill-CONTPID# Resume processkill-HUPPID# Hangup (reload config)kill-USR1PID# User-defined signal 1kill-0PID# Check if process exists# Kill by namekillallprocess_name
pkillprocess_name
pkill-f"pattern"# List all signalskill-l
# Process nicenessnice-n10command# Start with lower priorityrenice15-pPID# Change priority of running process# Run immune to hangupsnohupcommand&nohupcommand>output.log2>&1&# Limit runtimetimeout30command# Kill after 30 secondstimeout-sKILL60command
# Start, capture PID, waitcmd&pid=$!wait$pidecho"Exit status: $?"# Multiple background processesforfin*.dat;doprocess"$f"&donewait# Wait for all to finish# Parallel with limitmax_jobs=4forfin*.dat;dowhile[[$(jobs-r|wc-l)-ge$max_jobs]];dosleep0.1
doneprocess"$f"&donewait
# Set traptrap'commands'SIGNAL[SIGNAL...]# Remove traptrap-SIGNAL
# Ignore signaltrap''SIGNAL
# Show all trapstrap-p
# Show trap for specific signaltrap-pSIGINT
# Createmkdirdirname
mkdir-ppath/to/nested/dirs# Create parentsmkdir-m755dirname# With permissions# Removermdirdirname# Empty onlyrm-rfdirname# Non-empty# Navigatecd/absolute/path
cdrelative/path
cd~# Homecd-# Previous directorycd..# Parentcd../..# Grandparent# Directory stackpushd/some/path# Push and cdpopd# Pop and cd backdirs# Show stackdirs-v# Numbered stack# Listls# Basic listingls-la# Long format, hidden filesls-lh# Human-readable sizesls-lS# Sort by sizels-lt# Sort by timels-lR# Recursivels-1# One per linels-d*/# Directories onlyls-i# Show inodes# Tree viewtree# Full treetree-L2# Depth limittree-d# Directories onlytree-a# Include hiddentree-I'node_modules|.git'# Exclude patterns
# Basic searchgrep"pattern"file
# Common optionsgrep-i"pattern"file# Case insensitivegrep-v"pattern"file# Invert matchgrep-c"pattern"file# Count matchesgrep-n"pattern"file# Show line numbersgrep-l"pattern"files*# Show filenames onlygrep-L"pattern"files*# Files without matchgrep-w"word"file# Whole word matchgrep-x"line"file# Whole line matchgrep-r"pattern"directory/# Recursivegrep-rn"pattern".# Recursive with line numbersgrep-A3"pattern"file# 3 lines aftergrep-B3"pattern"file# 3 lines beforegrep-C3"pattern"file# 3 lines context (before+after)grep-o"pattern"file# Only matching partgrep-P"pattern"file# Perl regex (GNU grep)grep-E"pattern"file# Extended regex (same as egrep)grep-F"string"file# Fixed string (same as fgrep)grep-q"pattern"file# Quiet (exit status only)grep--include="*.py"-r"def"# Filter filesgrep--exclude="*.log"-r"err"# Exclude filesgrep--exclude-dir=".git"-r"x"# Exclude directoriesgrep-m5"pattern"file# Max 5 matchesgrep-h"pattern"files*# No filename prefixgrep-H"pattern"file# Always show filename# Multiple patternsgrep-e"pat1"-e"pat2"file
grep"pat1\|pat2"file
egrep"pat1|pat2"file
sortfile# Alphabetical sortsort-nfile# Numeric sortsort-rfile# Reverse sortsort-ufile# Unique linessort-k2file# Sort by column 2sort-k2,2nfile# Numeric sort by column 2sort-t:-k3-n/etc/passwd# Custom delimiter, sort by field 3sort-hfile# Human-readable sizes (1K, 2M)sort-Mfile# Month sort (Jan, Feb, ...)sort-Vfile# Version sort (1.0, 1.1, 1.10)sort-ffile# Case insensitivesort-Rfile# Random order (shuffle)sort-sfile# Stable sortsort-ooutputfile# Output to file (can be same as input)sort-zfile# Null-terminated linessort-cfile# Check if sorted
# cut โ extract columnscut-d:-f1/etc/passwd# Field 1, delimiter :cut-d,-f2,4data.csv# Fields 2 and 4cut-c1-10file# Characters 1-10cut-c5-file# From character 5 to endcut--complement-d:-f2file# All except field 2# paste โ merge linespastefile1file2# Side by side (tab-separated)paste-d,file1file2# Comma-separatedpaste-sfile# Serial (all on one line)paste-d'\n'file1file2# Interleave# join โ join files on common fieldjoinfile1file2# Join on first fieldjoin-12-21file1file2# Join field 2 of file1 with field 1 of file2join-t:file1file2# Custom delimiterjoin-a1file1file2# Show unmatched from file1join-v1file1file2# Only unmatched from file1
# uniq โ filter adjacent duplicates (sort first!)sortfile|uniq# Remove duplicatessortfile|uniq-c# Count occurrencessortfile|uniq-d# Show only duplicatessortfile|uniq-u# Show only unique linessortfile|uniq-i# Case insensitive# tr โ translate/delete characterstr'a-z''A-Z'<file# Lowercase to uppercasetr'[:lower:]''[:upper:]'<f# Same, POSIXtr-d'0-9'<file# Delete all digitstr-s' '<file# Squeeze repeated spacestr'\n'' '<file# Join lines with spacestr-dc'a-zA-Z0-9'</dev/urandom|head-c32# Random stringtr',''\t'<file# CSV to TSV# column โ format into columnscolumn-tfile# Auto-align columnscolumn-t-s:file# Custom separatorcolumn-s,-tdata.csv# Format CSV as table
# By namefind.-name"*.txt"# Case sensitivefind.-iname"*.txt"# Case insensitivefind.-not-name"*.log"# Exclude pattern# By typefind.-typef# Files onlyfind.-typed# Directories onlyfind.-typel# Symbolic linksfind.-typef-name"*.sh"# Combine# By sizefind.-size+10M# Larger than 10MBfind.-size-1k# Smaller than 1KBfind.-size0# Empty filesfind.-empty# Empty files and dirs# By timefind.-mtime-7# Modified in last 7 daysfind.-mtime+30# Modified more than 30 days agofind.-mmin-60# Modified in last 60 minutesfind.-atime-1# Accessed in last dayfind.-ctime-1# Changed in last dayfind.-newerreference_file# Newer than reference# By permissionsfind.-perm755# Exact permissionsfind.-perm-644# At least these permissionsfind.-perm/u+x# Any with user executefind.-executable# Executable files# By user/groupfind.-userusername
find.-groupgroupname
find.-uid1000find.-nouser# No matching user# Depth controlfind.-maxdepth2-name"*.txt"find.-mindepth1-maxdepth3# Action: execute commandfind.-name"*.log"-execrm{}\;find.-name"*.txt"-execgrep-l"word"{}\;find.-name"*.sh"-execchmod+x{}+# Batch mode# Action: printfind.-name"*.txt"-print
find.-name"*.txt"-print0# Null-terminated# Action: deletefind.-name"*.tmp"-delete
# Logical operatorsfind.\(-name"*.txt"-o-name"*.md"\)find.-name"*.log"-a-size+1M
find.-not-name"*.txt"find.!-name"*.txt"# Same as above# Practical examplesfind.-typef-name"*.py"-newersetup.py
find/var/log-typef-size+100M
find.-typef-mtime+90-delete
find.-typed-empty-delete
find.-typef-execfile{}\;|grep"text"
whichcommand# Path to commandwhich-acommand# All pathswhereiscommand# Binary, source, man pagetypecommand# How shell interprets nametype-acommand# All interpretationscommand-vcommand# POSIX way to check existencehash-r# Clear command hash cache
# Portsss-tlnp# TCP listening ports (Linux)netstat-tlnp# Older alternativelsof-i:8080# What's using port 8080lsof-iTCP# All TCP connections# Firewall (Linux)sudoiptables-L# List rulessudoufwstatus# UFW statussudoufwallow22# Allow SSHsudoufwenable# Enable firewall# Network monitoringnetstat-an# All connectionsss-s# Socket statisticsnmaphost# Port scannc-zvhost80# Test port connectivitync-l1234# Listen on port
uname-a# Full system infouname-r# Kernel versionuname-m# Machine architecturehostnamectl# System details (systemd)lsb_release-a# Distribution info (Linux)cat/etc/os-release# OS informationcat/proc/version# Kernel version# Hardwarelscpu# CPU infocat/proc/cpuinfo# Detailed CPUfree-h# Memory usagecat/proc/meminfo# Detailed memorylsblk# Block deviceslspci# PCI deviceslsusb# USB devicesdmidecode# BIOS/hardware info# macOS equivalentssw_vers# macOS versionsystem_profiler# Detailed hardwaresysctl-nmachdep.cpu.brand_string# CPU
# top/htoptop# Real-time processestop-o%MEM# Sort by memoryhtop# Interactive top# Process listingpsaux# All processespsauxf# Process treeps-eopid,ppid,%cpu,%mem,comm--sort=-%cpu|head
pstree# Process tree view# System uptimeuptime
w# Who + uptime + load# System loadcat/proc/loadavg
# Memoryfree-h
vmstat15# Virtual memory stats (5 seconds)# I/Oiostat15# I/O statsiotop# I/O by process# File accessstrace-pPID# Trace system callsltracecommand# Trace library calls
# User infowhoami# Current userid# User ID, groupsidusername# Specific userwho# Logged in usersw# Who + activitylast# Login historylastlog# Last login per userfingerusername# User info (if installed)# User management (requires root)useraddusername
useradd-m-s/bin/bashusername# With home dir, shelluserdelusername
userdel-rusername# Remove with home dirusermod-aGgroupusername# Add to groupusermod-s/bin/zshusername# Change shellpasswdusername# Set passwordchage-lusername# Password aging info# Group managementgroups# Current user's groupsgroupsusername# User's groupsgroupaddgroupname
groupdelgroupname
groupmod-nnewnameoldname
# Switch usersu-username# Login shellsudocommand# Run as rootsudo-uusercommand# Run as another usersudo-i# Root login shellsudo-s# Root shellvisudo# Edit sudoers safely# /etc/passwd format:# username:x:UID:GID:comment:home:shell# /etc/group format:# groupname:x:GID:member1,member2
brewupdate# Update Homebrewbrewupgrade# Upgrade all packagesbrewinstallpackage# Installbrewuninstallpackage# Removebrewsearchkeyword# Searchbrewinfopackage# Infobrewlist# List installedbrewdoctor# Check for issuesbrewcleanup# Remove old versionsbrewserviceslist# List servicesbrewservicesstartpackage# Start service
# Enableshopt-soption
# Disableshopt-uoption
# Queryshoptoption
# List allshopt# Useful optionsshopt-sautocd# cd by typing directory nameshopt-scdspell# Correct minor cd typosshopt-sdirspell# Correct dir names during completionshopt-sdotglob# Include hidden files in globshopt-sextglob# Extended pattern matchingshopt-sglobstar# ** recursive matchingshopt-shistappend# Append to history fileshopt-snocaseglob# Case-insensitive globbingshopt-snullglob# Unmatched globs expand to nothingshopt-scheckwinsize# Update LINES/COLUMNS after commandsshopt-scmdhist# Save multi-line commands as one entryshopt-slithist# Save multi-line with newlinesshopt-sprogcomp# Programmable completionshopt-spromptvars# Variable expansion in promptsshopt-ssourcepath# Use PATH for source/dot builtin
# Case insensitive completion
set completion-ignore-case on
# Show completions on first Tab
set show-all-if-ambiguous on
# Append slash to directories
set mark-directories on
# Color completions by file type
set colored-stats on
# Show common prefix then cycle
set menu-complete-display-prefix on
# vi mode
set editing-mode vi
# Key bindings
"\e[A": history-search-backward
"\e[B": history-search-forward
# Time a commandtimecommandtime{cmd1;cmd2;cmd3;}# Timing with more detail/usr/bin/time-vcommand# Verbose (Linux)/usr/bin/time-lcommand# Verbose (macOS)# Profile script with timestampsPS4='+ $(date "+%s.%N") 'bash-xscript.sh2>profile.log
# Benchmark with hyperfine (if installed)hyperfine'command1''command2'# Simple benchmark loopstart=$SECONDSforiin{1..1000};docommanddoneelapsed=$((SECONDS-start))echo"Elapsed: ${elapsed}s"
# Current date/timedate# Default formatdate-u# UTC# Format stringsdate+%Y-%m-%d# 2026-02-20date+%H:%M:%S# 14:30:45date"+%Y-%m-%d %H:%M:%S"# 2026-02-20 14:30:45date+%s# Unix timestampdate+%F# Same as %Y-%m-%ddate+%T# Same as %H:%M:%Sdate+%A# Full weekday namedate+%B# Full month namedate+%Z# Timezone namedate+%z# Timezone offsetdate"+%b %d, %Y"# Feb 20, 2026date-R# RFC 2822 formatdate-I# ISO 8601 format# Date arithmeticdate-d"+1 day"# Tomorrow (GNU)date-d"-1 week"# Last weekdate-d"next friday"# Next Fridaydate-d"2 months ago"# 2 months agodate-d@1708387200# From timestamp# macOS date arithmeticdate-v+1d# Tomorrowdate-v-1w# Last weekdate-v+2m# 2 months from nowdate-r1708387200# From timestamp# Convert between formatsdate-d"2026-02-20"+%s# Date to timestamp (GNU)date-d@1708387200+%Y-%m-%d# Timestamp to date (GNU)# Elapsed timestart=$(date+%s)# ... work ...end=$(date+%s)echo"Duration: $((end-start)) seconds"
# Always quote variablesecho"$variable"cp"$source""$dest"# Use [[ ]] instead of [ ][[-f"$file"]]&&echo"exists"# Check commands exist before usingcommand-vgit&>/dev/null||die"git is required"hashcurl2>/dev/null||die"curl is required"# Default valuesname="${1:-default}"config="${CONFIG_FILE:-/etc/myapp/config}"# Validate arguments[[$#-lt1]]&&die"Usage: $0 <filename>"[[-z"$1"]]&&die"Argument cannot be empty"[[-f"$1"]]||die"File not found: $1"[[-r"$1"]]||die"File not readable: $1"# Safe temporary filestmpfile=$(mktemp)||die"Failed to create temp file"trap'rm -f "$tmpfile"'EXIT
# Prevent partial writes (write to temp then move)tmpout=$(mktemp)generate_output>"$tmpout"mv"$tmpout""$final_output"# Use arrays for file lists (handle spaces)files=("file with spaces.txt""another file.txt")forfin"${files[@]}";doprocess"$f"done# Safe cdcd"$dir"||die"Cannot cd to $dir"# Avoid eval when possible# BAD: eval "$user_input"# GOOD: use arrays for commandscmd=(ls-la"$directory")"${cmd[@]}"
# Create named pipemkfifo/tmp/mypipe
# Writer (in one terminal)echo"Hello from writer">/tmp/mypipe
# Reader (in another terminal)cat</tmp/mypipe
# Bidirectional communicationmkfifo/tmp/pipe1/tmp/pipe2
# Process 1whiletrue;doread-rmsg</tmp/pipe1
echo"Got: $msg"echo"Reply: ACK">/tmp/pipe2
done# Process 2echo"Hello">/tmp/pipe1
read-rreply</tmp/pipe2
echo"$reply"# Cleanuprm/tmp/mypipe/tmp/pipe1/tmp/pipe2
# GNU Parallel (if available)find.-name"*.jpg"|parallelconvert{}{.}.png
caturls.txt|parallel-j4curl-sO{}parallel-j8gzip:::*.log
# Native bash parallelismmax_procs=4pids=()forfilein*.dat;doprocess"$file"&pids+=($!)# Limit concurrencywhile((${#pids[@]}>=max_procs));dowait-n# Wait for any to finish (Bash 4.3+)# Remove finished PIDsnew_pids=()forpidin"${pids[@]}";dokill-0"$pid"2>/dev/null&&new_pids+=("$pid")donepids=("${new_pids[@]}")donedonewait# Wait for remaining
# Extract unique IPs from loggrep-oE'\b([0-9]{1,3}\.){3}[0-9]{1,3}\b'access.log|sort-u
# Count unique IPsgrep-oE'\b([0-9]{1,3}\.){3}[0-9]{1,3}\b'access.log|sort|uniq-c|sort-rn
# Extract email addressesgrep-oE'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'file
# Remove blank linessed'/^$/d'file
grep-v'^$'file
# Remove comments and blank linesgrep-Ev'^\s*(#|$)'file
# Number non-empty linesgrep-n'.'file
# Print specific columns from CSVawk-F,'{print $1, $3}'data.csv
# Sum a columnawk'{sum += $1} END {print sum}'numbers.txt
# Top 10 most common wordstr-s' ''\n'<file|sort|uniq-c|sort-rn|head-10
# Convert CSV to JSON (basic)awk-F,'NR==1{split($0,h);next}{printf "{"; for(i=1;i<=NF;i++) printf "\"%s\":\"%s\"%s",h[i],$i,(i<NF?",":""); print "}"}'data.csv
# Replace ^M (carriage return) โ DOS to Unixsed-i's/\r$//'file
tr-d'\r'<file>clean_file
# Generate sequenceseq1100printf'%s\n'{001..100}# Random line from fileshuf-n1file
sort-Rfile|head-1
# Who is using the most disk spacedu-sh/home/*|sort-rh|head
# Find files modified in last 24 hoursfind/var/log-mtime-1-typef
# Monitor file changesinotifywait-m-r/path/to/watch# (requires inotify-tools)fswatch/path/to/watch# macOS# Kill all processes by namepkill-9process_name
# Check if port is in uselsof-i:8080|grepLISTEN
# HTTP health checkcurl-sfhttp://localhost:8080/health>/dev/null&&echo"UP"||echo"DOWN"# Simple HTTP server (Python)python3-mhttp.server8080# Watch log file with highlightingtail-f/var/log/syslog|grep--line-buffered--color"ERROR\|WARNING\|"# Backup MySQL databasemysqldump-uroot-pdatabase>backup_$(date+%F).sql
# Monitor system resourceswatch-n1'free -h; echo "---"; df -h; echo "---"; uptime'# Find broken symlinksfind.-xtypel
# List all cron jobs for all usersforuserin$(cut-f1-d:/etc/passwd);doecho"--- $user ---"crontab-u"$user"-l2>/dev/null
done# System boot timewho-b
uptime-s
# Check SSL certificate expiryecho|openssls_client-connectexample.com:4432>/dev/null|opensslx509-noout-dates
# Download all links on a pagecurl-shttps://example.com|grep-oP'href="\K[^"]+'|wget-i-
# Test port connectivitytimeout3bash-c'</dev/tcp/host/port'&&echo"Open"||echo"Closed"# Get public IPcurl-sifconfig.me
curl-sicanhazip.com
# DNS lookup all recordsdiganyexample.com+noall+answer
# Monitor HTTP response timeswhiletrue;docurl-so/dev/null-w"%{time_total}\n"https://example.com
sleep5done# Simple port scannerforportin{1..1024};do(echo>/dev/tcp/host/$port)2>/dev/null&&echo"Port $port open"done
# Basic heredoccat<<EOFHello, $USER!Today is $(date).Current directory: $PWDEOF# No variable expansion (quoted delimiter)cat<<'EOF'This is literal: $USERNo expansion: $(date)EOF# Indented heredoc (strip leading tabs)cat<<-EOF This is indented But tabs are stripped Variables: $USEREOF# To a filecat>config.ini<<EOF[database]host=localhostport=5432EOF# Pipe to commandmysql-uroot<<EOFCREATE DATABASE mydb;USE mydb;CREATE TABLE users (id INT, name VARCHAR(50));EOF# With sudosudotee/etc/config.conf>/dev/null<<EOFsetting=valueanother=settingEOF# Heredoc in functiongenerate_html(){cat<<EOF<!DOCTYPE html><html><head><title>$1</title></head><body><h1>$1</h1></body></html>EOF}generate_html"My Page">page.html
# Feed a string to command's stdinbc<<<"2 + 3"# => 5grep"pattern"<<<"$string"read-raarr<<<"one two three"# Variable-basedinput="hello world"wc-w<<<"$input"# => 2# Multi-wordcut-d','-f2<<<"a,b,c"# => b
# Run in subshell (separate process)(cd/tmp&&ls)# Current dir unchangedecho"$PWD"# Still original directory# Isolate variables(x=100echo"Inside: $x"# => 100)echo"Outside: $x"# => (empty)# Isolate options(set-e
failing_command# Exits subshell, not parent)echo"Parent continues"# Parallel subshells(task1)&(task2)&(task3)&wait
# Run in CURRENT shell (no subshell){cd/tmp
echo"Now in: $PWD"}echo"Still in: $PWD"# /tmp! (changed in current shell)# Redirect group output{echo"Line 1"echo"Line 2"echo"Line 3"}>output.txt
# Conditional execution group[[-fconfig.txt]]&&{sourceconfig.txt
echo"Loaded config"}# Error handling group{command1&&command2&&command3
}||{echo"Something failed"exit1}
# Move cursortputcup1020# Move to row 10, col 20tputcuu5# Move up 5 linestputcud5# Move down 5 linestputcuf10# Move forward 10 columnstputcub10# Move backward 10 columnstputsc# Save cursor positiontputrc# Restore cursor position# Cleartputclear# Clear screentputel# Clear to end of linetputed# Clear to end of screen# Terminal infotputcols# Terminal widthtputlines# Terminal heighttputcolors# Number of colors supported# Hide/show cursortputcivis# Hide cursortputcnorm# Show cursor
# Edit crontabcrontab-e
# List crontabcrontab-l
# Remove all cron jobscrontab-r
# Crontab format:# MIN HOUR DOM MON DOW command# * * * * *# โ โ โ โ โ# โ โ โ โ โโโ Day of Week (0-7, 0 & 7 = Sunday)# โ โ โ โโโโโโโ Month (1-12)# โ โ โโโโโโโโโโโโ Day of Month (1-31)# โ โโโโโโโโโโโโโโโโโ Hour (0-23)# โโโโโโโโโโโโโโโโโโโโโโ Minute (0-59)# Examples*****command# Every minute*/5****command# Every 5 minutes0****command# Every hour09***command# Daily at 9 AM09**1-5command# Weekdays at 9 AM001**command# First of every month00**0command# Every Sunday midnight@rebootcommand# At startup@hourlycommand# Every hour@dailycommand# Daily at midnight@weeklycommand# Weekly@monthlycommand# Monthly@yearlycommand# Yearly# at command (one-time scheduling)echo"command"|at2pmtomorrow
atnow+5minutes<<<"command"at14:002026-03-01<<<"command"atq# List pending jobsatrmjob_number# Remove job