Bash Shell

Bash cheatsheet — variables, loops, conditionals, arrays, functions, string manipulation. if/else, for/while, $(), $?, ${var//find/replace}. Real script patterns.

16 min read

What it is

Bash is the default command-line interpreter (shell) on most Linux and macOS systems, used for executing commands, scripting automation, and interacting with the operating system.

Installation

Bash is pre-installed on most Linux and macOS systems. If you need to install it separately or on a different OS:

Linux (Debian/Ubuntu):

sudo apt update
sudo apt install bash

Linux (Fedora/CentOS/RHEL):

sudo dnf install bash
# or
sudo yum install bash

macOS: Bash is usually pre-installed. If not, or for an updated version, use Homebrew:

brew install bash

After installation, you might need to configure it as your default shell (e.g., by editing /etc/shells and using chsh -s /path/to/bash).

Windows: Via the Windows Subsystem for Linux (WSL):

  1. Install WSL from the Microsoft Store or via PowerShell: wsl --install
  2. Open your chosen Linux distribution (e.g., Ubuntu) from the Start Menu. Bash will be available.

Core Concepts

  • Shell: An interpreter that takes commands from the user and tells the operating system to execute them. Bash is one such interpreter.
  • Prompt: The text displayed by the shell indicating it’s ready to accept commands. It typically shows the username, hostname, and current directory (e.g., user@hostname:~/current/dir$).
  • Command: An instruction given to the shell. It usually consists of a command name, followed by options (flags) and arguments.
  • Path: The location of a file or directory in the file system hierarchy.
  • Environment Variables: Variables whose values can affect the way running processes behave. PATH (where to find executables), HOME (user’s home directory), USER (current username) are common examples.
  • Standard Streams:
    • Stdin (0): Standard input, where commands typically read data from (usually the keyboard).
    • Stdout (1): Standard output, where commands typically write their results (usually the terminal screen).
    • Stderr (2): Standard error, where commands typically write error messages (usually the terminal screen).
  • Redirection: Changing where Stdin, Stdout, or Stderr go.
  • Piping: Connecting the Stdout of one command to the Stdin of another.
  • Wildcards/Globbing: Special characters (*, ?, []) used to match filenames.
  • Job Control: Managing background and foreground processes.
  • Scripting: Writing a sequence of Bash commands in a file to automate tasks.

Commands / Usage

  • pwd Print working directory. Shows your current location.
    pwd
    # Output: /home/user/projects
    
  • cd <directory> Change directory. Moves you to a specified directory.
    cd /var/log
    cd ../../etc # Go up two levels, then into etc
    cd ~          # Go to your home directory
    cd -          # Go to the previous directory
    
  • ls List directory contents. Shows files and subdirectories.
    ls
    # Output: file1.txt  mydir  script.sh
    
  • ls -l List in long format. Shows permissions, owner, size, modification date.
    ls -l
    # Output: -rw-r--r-- 1 user group 1024 Jan 15 10:30 file1.txt
    
  • ls -a List all files, including hidden ones (starting with .).
    ls -a
    # Output: .  ..  .bashrc  file1.txt  mydir
    
  • ls -lh List in long format with human-readable sizes (e.g., 1K, 23M, 2G).
    ls -lh
    # Output: -rw-r--r-- 1 user group 1.1K Jan 15 10:30 file1.txt
    

Managing Files and Directories

  • mkdir <directory_name> Make directory. Creates a new directory.
    mkdir new_project
    mkdir -p project/src/components # Create parent directories if they don't exist
    
  • touch <file_name> Create empty file or update timestamp. Creates a file if it doesn’t exist, or updates its modification time if it does.
    touch report.txt
    touch log_$(date +%Y%m%d).csv
    
  • cp <source> <destination> Copy files or directories.
    cp file1.txt file1_backup.txt
    cp mydir /tmp/mydir_copy
    cp -r project_a project_b # Copy directories recursively
    cp -v file.txt /backup/ # Verbose output, show what's being copied
    
  • mv <source> <destination> Move or rename files or directories.
    mv old_name.txt new_name.txt # Rename
    mv file.txt /data/          # Move to a different directory
    mv dir1 dir2                # Rename/move directory
    
  • rm <file_name> Remove files. Use with caution!
    rm temp.log
    rm -i file.txt # Interactive, prompt before removing
    rm -f *.tmp    # Force removal, ignore nonexistent files, no prompts
    
  • rmdir <directory_name> Remove empty directory.
    rmdir empty_folder
    
  • rm -r <directory_name> Remove directories and their contents recursively. EXTREMELY DANGEROUS.
    rm -r old_project # Deletes the entire project directory and all its contents
    rm -rf temp_dir   # Force recursive removal without prompts
    

Viewing and Editing Files

  • cat <file_name> Concatenate and display file content. Good for small files.
    cat /etc/hosts
    cat file1.txt file2.txt > combined.txt # Concatenate two files into a new one
    
  • less <file_name> View file content interactively. Allows scrolling forward and backward. Press q to quit.
    less large_log_file.log
    
  • head <file_name> Display the first 10 lines of a file.
    head data.csv
    head -n 20 data.csv # Display first 20 lines
    
  • tail <file_name> Display the last 10 lines of a file.
    tail error.log
    tail -n 50 error.log # Display last 50 lines
    tail -f error.log    # Follow file, continuously display new lines as they are added
    
  • nano <file_name> Simple, beginner-friendly text editor.
    nano config.yml
    
  • vim <file_name> or vi <file_name> Powerful, modal text editor. Requires learning its commands (insert mode, normal mode, visual mode).
    vim script.sh
    

Searching and Filtering

  • grep <pattern> <file_name> Search for patterns in files.
    grep "error" /var/log/syslog
    grep -i "warning" app.log # Case-insensitive search
    grep -r "function_name" src/ # Recursive search in directory
    grep -v "debug" log.txt    # Invert match: show lines NOT containing "debug"
    grep -E "error|warning" logs.txt # Extended regex: match "error" OR "warning"
    ps aux | grep nginx # Find running nginx processes
    
  • find <path> -name <pattern> Find files by name.
    find . -name "*.log"      # Find all files ending in .log in current dir and subdirs
    find /etc -type f -name "host*" # Find files starting with "host" in /etc
    find /tmp -mtime +7 -delete # Find and delete files older than 7 days in /tmp
    
  • find <path> -type <f|d> Find files (f) or directories (d).
    find . -type d -name "config" # Find directories named "config"
    
  • find <path> -mtime <n> Find files modified n days ago. +n for more than n days, -n for less than n days.
    find /var/log -mtime +30 # Files modified more than 30 days ago
    
  • wc <file_name> Word count. Counts lines, words, and characters.
    wc report.txt
    # Output: 15 120 850 report.txt (lines words bytes)
    wc -l < file.txt # Count only lines
    

Permissions

  • chmod <mode> <file/directory> Change file mode bits (permissions).

    chmod 755 script.sh      # rwxr-xr-x (owner: rwx, group: r-x, others: r-x)
    chmod +x script.sh       # Add execute permission for owner, group, and others
    chmod u+w file.txt       # Add write permission for the user (owner)
    chmod go-rwx data.txt    # Remove read, write, execute for group and others
    chmod a+r public_file.txt # Add read permission for all (user, group, others)
    
    • Numeric mode: r=4, w=2, x=1. Sum for each category (user, group, others). 755 = (4+2+1)(4+0+1)(4+0+1)
    • Symbolic mode: u (user), g (group), o (others), a (all). + (add), - (remove), = (set exactly).
  • chown <user>:<group> <file/directory> Change file owner and group. Requires root privileges (sudo).

    sudo chown www-data:www-data /var/www/html/index.html
    sudo chown user1:group1 file.txt
    sudo chown -R user1:group1 project_dir # Recursively change ownership
    
  • chgrp <group> <file/directory> Change file group.

    chgrp developers report.txt
    

Process Management

  • ps Report a snapshot of the current processes.
    ps aux # Show all processes for all users in BSD format
    ps -ef # Show all processes in System V format
    
  • top Display dynamic real-time view of running processes. Press q to quit.
    top
    
  • htop An interactive process viewer (often needs installation: sudo apt install htop). More user-friendly than top.
    htop
    
  • kill <PID> Send a signal to a process (default signal is TERM - terminate).
    kill 12345 # Send SIGTERM to process ID 12345
    kill -9 12345 # Send SIGKILL (force kill) to process ID 12345
    kill -HUP 12345 # Send SIGHUP (hang up), often used to reload config
    
  • pkill <process_name> Kill processes based on their name.
    pkill nginx
    pkill -f "my_long_running_script.py" # Kill process whose command line matches pattern
    
  • pgrep <process_name> Find the process ID (PID) of a process.
    pgrep sshd
    
  • bg Put the most recently stopped background job into the background.
  • fg Bring the most recently stopped background job to the foreground.
  • jobs List currently running background jobs.
  • Ctrl+Z Suspend the current foreground process (stops it, doesn’t kill it). Can then use bg or fg.

Input/Output Redirection and Piping

  • > Redirect standard output. Overwrites the file.
    ls -l > file_listing.txt
    echo "Hello World" > greeting.txt
    
  • >> Append standard output to a file.
    echo "Another line" >> greeting.txt
    date >> system_log.txt
    
  • < Redirect standard input from a file.
    sort < unsorted_list.txt > sorted_list.txt
    grep "pattern" < input.txt
    
  • 2> Redirect standard error.
    find / -name "*.conf" 2> find_errors.log # Redirect only errors
    
  • &> or >& Redirect both standard output and standard error.
    ./my_script.sh &> script_output_and_errors.log
    
  • | (Pipe) Send standard output of the command on the left to the standard input of the command on the right.
    ls -l | grep ".txt" # List files, then filter for lines containing ".txt"
    cat access.log | grep "404" | wc -l # Count 404 errors in access log
    ps aux | sort -k 3 -nr # List processes, sort by CPU usage (descending)
    
  • 2>&1 Redirect stderr to stdout. Often used with pipes or redirection to capture both.
    ./my_script.sh 2>&1 | tee script_log.txt # Capture stdout and stderr, and also print to terminal
    ./my_script.sh > output.log 2>&1       # Redirect stdout to file, stderr to stdout (which is redirected to file)
    

Environment Variables

  • echo $<VARIABLE_NAME> Display the value of an environment variable.
    echo $HOME
    echo $PATH
    echo $USER
    
  • export <VARIABLE_NAME>=<value> Set and export an environment variable, making it available to child processes.
    export MY_API_KEY="abcdef12345"
    export PATH="$PATH:/usr/local/bin" # Add a directory to the PATH
    
  • unset <VARIABLE_NAME> Unset (remove) an environment variable.
    unset MY_API_KEY
    
  • env Display all environment variables.
  • printenv Print all or some environment variables.

Archiving and Compression

  • tar -cvf archive.tar <file1> <dir1> Create a tar archive (without compression).
    tar -cvf backup.tar /home/user/documents
    
  • tar -xvf archive.tar Extract a tar archive.
    tar -xvf backup.tar -C /mnt/restore_point # Extract to a specific directory
    
  • tar -czvf archive.tar.gz <file1> <dir1> Create a gzipped tar archive.
    tar -czvf website_backup.tar.gz /var/www/html
    
  • tar -xzvf archive.tar.gz Extract a gzipped tar archive.
    tar -xzvf website_backup.tar.gz
    
  • tar -cjvf archive.tar.bz2 <file1> <dir1> Create a bzip2 compressed tar archive.
    tar -cjvf large_archive.tar.bz2 huge_data/
    
  • tar -xjvf archive.tar.bz2 Extract a bzip2 compressed tar archive.
    tar -xjvf large_archive.tar.bz2
    
  • gzip <file> Compress a file using gzip (creates .gz file, removes original).
    gzip large_log.txt
    # Creates large_log.txt.gz
    
  • gunzip <file.gz> Decompress a gzip file.
    gunzip large_log.txt.gz
    
  • zip archive.zip <file1> <dir1> Create a zip archive.
    zip -r project_files.zip src/ config/
    
  • unzip archive.zip Extract a zip archive.
    unzip project_files.zip
    

Networking

  • ping <host> Send ICMP ECHO_REQUEST packets to network hosts.
    ping google.com
    ping -c 4 192.168.1.1 # Send 4 pings
    
  • curl <URL> Transfer data from or to a server. Primarily for HTTP/HTTPS.
    curl https://api.example.com/data
    curl -O https://example.com/file.zip # Download file and save with original name
    curl -L https://example.com # Follow redirects
    curl -X POST -d '{"key":"value"}' https://api.example.com/resource # POST request
    curl -I https://example.com # Get headers only
    
  • wget <URL> Non-interactive network downloader.
    wget https://example.com/dataset.csv
    wget -r -np -nH --cut-dirs=1 https://example.com/files/ # Recursive download, no parent dirs, skip host dir, cut dir depth
    
  • ssh <user>@<host> Connect to a remote machine using the Secure Shell protocol.
    ssh john@server.example.com
    ssh -p 2222 user@host.com # Connect using a non-standard port
    
  • scp <source> <user>@<host>:<destination> Securely copy files between hosts.
    scp local_file.txt user@remote_server:/path/to/destination/
    scp user@remote_server:/path/to/remote_file.txt . # Copy from remote to local current dir
    scp -r local_directory user@remote_server:/path/
    

Shell Scripting Basics

  • #!/bin/bash Shebang line. Specifies the interpreter for the script. Must be the first line.
    #!/bin/bash
    echo "Hello from my script!"
    
  • variable=value Assign a value to a variable. No spaces around =.
    NAME="Alice"
    COUNT=10
    
  • echo $variable Print the value of a variable.
    echo "Hello, $NAME!"
    
  • read <variable_name> Read input from stdin into a variable.
    echo "Enter your name:"
    read USER_NAME
    echo "Hello, $USER_NAME"
    
  • if [ condition ]; then ... fi Conditional execution.
    if [ $count -gt 5 ]; then
        echo "Count is greater than 5"
    fi
    
    • Common conditions: -eq (equal), -ne (not equal), -gt (greater than), -ge (greater or equal), -lt (less than), -le (less or equal), -z (string is empty), -n (string is not empty).
  • for item in list; do ... done Looping construct.
    for i in {1..5}; do
        echo "Number: $i"
    done
    
    for file in *.txt; do
        echo "Processing $file"
        # process "$file"
    done
    
  • while condition; do ... done Loop while a condition is true.
    counter=0
    while [ $counter -lt 3 ]; do
        echo "Loop iteration $counter"
        counter=$((counter + 1)) # Arithmetic expansion
    done
    
  • $(command) or `command` Command substitution. Executes a command and substitutes its output.
    CURRENT_DIR=$(pwd)
    echo "You are in $CURRENT_DIR"
    files=`ls *.log`
    echo "Log files found: $files"
    
  • $((expression)) Arithmetic expansion. For integer arithmetic.
    SUM=$((5 + 3))
    echo $SUM # Output: 8
    

Wildcards (Globbing)

  • * Matches zero or more characters.
    ls *.txt       # List all files ending in .txt
    cp data/* /backup/ # Copy all files in data/ to /backup/
    
  • ? Matches exactly one character.
    ls file?.log   # Matches file1.log, fileA.log, but not file10.log
    
  • [] Matches any single character within the brackets.
    ls [abc]*.txt  # Matches a.txt, b.txt, c.txt, and also abc.txt
    ls file[0-9].log # Matches file0.log through file9.log
    ls [!a-z]*.sh   # Matches any .sh file NOT starting with a lowercase letter
    

Other Useful Commands

  • alias <name>='<command>' Create a shortcut (alias) for a command.
    alias ll='ls -lh'
    alias update='sudo apt update && sudo apt upgrade -y'
    
  • unalias <name> Remove an alias.
    unalias ll
    
  • history Display a list of previously executed commands.
    history 10 # Show last 10 commands
    !101       # Re-execute command number 101 from history
    !!         # Re-execute the last command
    
  • man <command> Display the manual page for a command.
    man ls
    man grep
    
  • sudo <command> Execute a command as the superuser (root). Requires password.
    sudo systemctl restart nginx
    
  • df -h Display disk space usage in human-readable format.
    df -h
    
  • du -sh <directory> Display disk usage of a directory in human-readable format (summary).
    du -sh /var/log
    
  • uname -a Print system information.
    uname -a
    # Output: Linux hostname 5.15.0-91-generic #102-Ubuntu SMP Tue Nov 14 13:30:08 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
    
  • clear Clear the terminal screen.

Common Patterns

  • Finding large files/directories:
    # Find top 10 largest files in current directory and subdirectories
    du -ah . | sort -rh | head -n 10
    
    # Find top 5 largest directories in /var/log
    du -sh /var/log/* | sort -rh | head -n 5
    
  • Automating log analysis:
    # Count occurrences of 'ERROR' in all .log files in /var/log, case-insensitive
    grep -ri "error" /var/log/*.log | wc -l
    
    # Show the last 100 lines of a log file, then follow new entries
    tail -n 100 -f /var/log/syslog
    
  • Quickly downloading a file:
    curl -O https://example.com/path/to/file.zip
    
  • Checking if a service is running:
    pgrep nginx > /dev/null && echo "Nginx is running" || echo "Nginx is NOT running"
    # or using systemctl (if available)
    systemctl is-active nginx --quiet && echo "Nginx is active" || echo "Nginx is inactive"
    
  • Executing commands remotely:
    ssh user@host.com "ls -l /tmp"
    ssh user@host.com "sudo systemctl restart apache2"
    
  • Copying files to/from remote servers:
    # Copy local file to remote server
    scp myconfig.conf user@server.com:/etc/myapp/
    
    # Copy remote file to local machine
    scp user@server.com:/var/log/app.log .
    
    # Copy local directory recursively
    scp -r ./my_project user@server.com:/home/user/projects/
    
  • Finding processes by name and killing them:
    # Find PID of 'bad_process'
    pgrep bad_process
    
    # Kill all processes named 'bad_process' forcefully
    pkill -9 bad_process
    
  • Chaining commands with && and ||: && (AND): Execute the next command only if the previous one succeeded (returned exit code 0). || (OR): Execute the next command only if the previous one failed (returned non-zero exit code).
    # Update package list, then upgrade packages if update was successful
    sudo apt update && sudo apt upgrade -y
    
    # Try to create a directory, if it fails, print an error
    mkdir /data/new_dir || echo "Failed to create directory /data/new_dir"
    
    # Compile code, then run if compilation succeeded
    gcc main.c -o myapp && ./myapp
    
  • Running commands in the background: Append & to a command to run it in the background.
    sleep 60 & # Sleep for 60 seconds in the background
    ./long_running_script.sh &
    
  • Saving command output to a file and displaying it:
    ./my_script.sh | tee output.log # Both displays on screen AND saves to output.log
    

Gotchas

  • Quoting: Unquoted spaces in filenames or arguments will cause errors or unexpected behavior. Use quotes (" ") or escape spaces (\ ).
    # Incorrect:
    # cd My Documents/
    
    # Correct:
    cd "My Documents/"
    cd My\ Documents/
    
  • rm -rf is irreversible: There is no "undelete" command. Double-check paths before using rm -rf.
  • Variable expansion: Variables are expanded before the command is executed. Be mindful of what the shell sees after expansion.
    files="file1.txt file2.txt"
    rm $files # This is equivalent to rm file1.txt file2.txt
    # If you wanted to remove a single file named "file1.txt file2.txt", it would fail.
    
  • Globbing vs. Literal Strings: Wildcards (*, ?, []) are expanded by the shell before being passed as arguments to a command, unless quoted.
    echo * # Prints all files/dirs in the current directory
    echo "*" # Prints the literal asterisk character
    
  • PATH variable: If a command isn’t found, it might be because its directory isn’t in your $PATH environment variable.
  • Exit Codes: Commands return an exit code (0 for success, non-zero for failure). This is used by && and ||. Use echo $? to see the exit code of the last command.
    ls / # Should succeed (exit code 0)
    echo $?
    ls /nonexistent_directory # Should fail (exit code non-zero)
    echo $?
    
  • Permissions: Many commands (like chown, systemctl, editing system files) require root privileges. Use sudo.
  • Shell vs. Program: Remember that the shell interprets commands and performs expansions/redirections before passing arguments to the actual program. This is why echo * behaves differently from echo "*".
  • cd is a shell built-in: You cannot run cd in a subshell and expect the parent shell’s directory to change.
    # This won't change your current directory
    bash -c "cd /tmp"
    pwd
    
    But this works:
    (cd /tmp && pwd) # Subshell runs cd, prints /tmp, parent shell is unaffected
    pwd
    
    Or directly:
    cd /tmp
    pwd