Skip to content

Bash Scripting

  • Bash scripting cheatsheet

  • 30 Bash Script Examples – Linux Hint

  • Shell: A command-line interpreter that acts as the interface between the user and the operating system kernel.

  • sh (Bourne Shell): The original Unix shell developed in 1977. It is the syntax standard but lacks modern features like command history or advanced tab completion.

  • Bash (Bourne Again Shell): An enhanced version of sh and the default shell for most Linux distributions. It is backward-compatible with sh but adds features like arrays and improved scripting logic.

  • Shell Scripting: The process of writing a series of commands in a text file to be executed by a shell. It is primarily used for automating repetitive tasks, system administration, and batch processing.

sh is the specification; Bash is a popular implementation of that specification; Shell Scripting is the application of either to automate logic.

image.png

image.png

Creating the Scripts and Making Executables.

Section titled “Creating the Scripts and Making Executables.”

image.png

  • Automate Boring Tasks or Repetitive Tasks.
  • Reduce Errors.
  • Installing / Configuring / Updating Software, Servers, etc.
  • Backups / Restore.
  • Mount new volumes.
  • Adding and configuring new users’ privileges.
  • System / Network Monitoring.

image.png

image.png

image.png

image.png

  • [ ] (The Classic/Portable Way):
    • It is actually a command, not just syntax.
    • Pros: It is “portable,” meaning it works in almost every shell (sh, bash, zsh, etc.).
    • Cons: It is basic and “lacks functionality” compared to the modern version.
  • [[ ]] (The Modern/Bash Way):
    • It is a built-in keyword specifically for Bash.
    • Pros: It is safer, supports regular expressions (regexp), and allows for wildcards.
    • Cons: It won’t work in older or strictly Bourne-compatible shells.

Example:

Terminal window
file="script.sh"
if [[ $file == *.sh ]]; then
echo "This is a shell script."
fi

→ supports regex and wildcards like *

  • Defined using parentheses, such as MY_ARRAY=("val1" "val2" "val3").
  • Indexing: Zero-indexed by default, meaning the first item is at position 0, though the provided table uses 1-based indexing for its specific examples.
  • Data Types: Bash arrays are heterogeneous, meaning you can mix strings, integers, and other types in a single array.
  • Elements are stored as a list of indexed strings.
Terminal window
# Define array
FRUITS=("apple" "banana" "cherry" "date" "elderberry")
# 1. Get second element: "banana"
echo ${FRUITS[1]}
# 2. Get length of "banana": 6
echo ${#FRUITS[1]}
# 3. Total elements: 5
echo ${#FRUITS[@]}
# 4. Slice (start index 3, get 2): "date elderberry"
echo ${FRUITS[@]:3:2}
# 5. Replace "apple" with "apricot"
echo ${FRUITS[@]/apple/apricot}
# 6. Delete element at index 2 (cherry)
unset FRUITS[2]
# 7. Iterate through remaining
for i in "${FRUITS[@]}"; do
echo "I like $i"
done
CommandDescription
${VAR[1]}Retrieves the element at index 1.
${VAR[@]}Expands to all elements currently in the array.
${#VAR[1]}Returns the character count (length) of the element at index 1.
${#VAR[@]}Returns the total count of elements stored in the array.
${VAR[@]:3:2}Slices the array to get 2 elements starting from index 3.
${VAR[@]/foo/bar}Returns all elements, replacing occurrences of “foo” with “bar”.
unset VAR[2]Deletes the element located at index 3 (specifically the third position in 0-indexed shells).
for i in "${VAR[@]}"A standard loop to perform an action on every item in the array.
  • Environment variables are dynamic values stored within your system that influence how applications and the shell behave. You can think of them as a “settings profile” for your computer’s terminal.
  • Definition: A named value (KEY=value) used by the system to store information like your default editor, system language, or where programs are located.
  • Format: Conventionally written in UPPER CASE and are case-sensitive.

Example: USER=root tells the system who is currently logged in.

FeatureShell VariableEnvironment Variable
ScopeOnly the current terminal window.Current terminal + any “child” programs started from it.
CreationFOO="bar".export FOO="bar".
PersistenceLost when you close the terminal.Can be made permanent by adding to ~/.bashrc.

image.png

Shell Variable Example

image.png

Environment Variable

image.png

→ Note that, once a variable is environment variable.. it can be accessed by other shell sessions as well. Environment variables are variables that are available system-wide and are inherited by all spawned child processes and shells.

PATH is the most important environment variable. It is a list of directories where the system looks for executable files (commands).

  • Example:

    • The Problem: If you download a tool like maestro-cli to a custom folder, the terminal won’t recognize the command or2help.
    • The Solution: You add that folder’s location to your PATH variable.

    # Adding a new folder to the existing PATH export PATH=$PATH:/home/maestro-cli/bin

    Now, the system will check /home/maestro-cli/bin whenever you type a command.

  • printenv: Shows all your current environment variables.

  • export: Promotes a local shell variable to an environment variable.

  • unset: Deletes a variable.

  • source ~/.bashrc: Forces the system to read your configuration file and apply changes immediately.

image.png

Proof that Shell variables Can’t be accessed from Child Process of same shell session..

image.png

Note: bash -c RUNs commands in child process of current shell

image.png

Example: bash script.sh "one" "two" "three four"

#!/bin/bash
echo "--- 1. Using \$* (Unquoted) ---"
for i in $*; do echo "<$i>"; done
# Logic: Splits "three four" into two words.
echo -e "\n--- 2. Using \$@ (Unquoted) ---"
for i in $@; do echo "<$i>"; done
# Logic: Same as above; without quotes, it's identical to $*.
echo -e "\n--- 3. Using \"\$*\" (Quoted) ---"
for i in "$*"; do echo "<$i>"; done
# Logic: Everything is mashed into ONE single string.
echo -e "\n--- 4. Using \"\$@\" (Quoted) ---"
for i in "$@"; do echo "<$i>"; done
# Logic: Preserves the original 3 arguments exactly.
SyntaxHow it sees “one” “two” “three four”Total Items
$*<one> <two> <three> <four>4
$@<one> <two> <three> <four>4
"$*"<one two three four>1
"$@"<one> <two> <three four>3
VariablePurpose
$?Exit status (0 = success, 1+ = fail).
$$Process ID of the current shell.
$#Count of arguments.
$0Name of the running script.
$nSpecific argument by position (1, 2, 3…).
$!Process ID of the last background job.
$-Currently active shell flags.
$_Last argument of the previous command.
#!/bin/bash
echo "No.of Command Line args = $#"
echo "Current script PID = $$"
echo "<---- START ---->"
ls
echo "<---- DONE ---->"
echo "Exit Code of above ls command = $?"
echo "<---- START ---->"
random_command
# This will now correctly show a non-zero exit code (likely 127)
echo "Exit code of above failed command = $?"
echo "<---- DONE ---->"
echo "<---- START ---->"
echo "sleeping for 3s in background"
sleep 3 & # <--- Added '&' to make it a background job
echo "<---- DONE ---->"
# Now $! will show the PID of the sleep process
echo "PID of Previous Sleep BG Job = $!"

image.png


following script will compress and upload all log files following pattern _log<NUM>.log of local machine into .tar and uploads to specified AWS S3 Bucket.. using AWS CLI..

Terminal window
**ls ./Desktop/server_logs/**
-rw-r--r--. 1 pavan_bandaru pavan_bandaru 0 Feb 16 12:01 app_log1.log
-rw-r--r--. 1 pavan_bandaru pavan_bandaru 0 Feb 16 12:01 app_log2.log
-rw-r--r--. 1 pavan_bandaru pavan_bandaru 0 Feb 16 12:01 app_log3.log
-rw-r--r--. 1 pavan_bandaru pavan_bandaru 0 Feb 16 12:01 app_log4.log
-rw-r--r--. 1 pavan_bandaru pavan_bandaru 0 Feb 16 12:01 app_log5.log
#!/bin/bash
# 1. Corrected logic to -ne (not equal) and added spaces for the test operator
[ $# -ne 2 ] && echo "Usage: $0 <local_path> <aws_bucket_name>" && exit 1
LOCAL_PATH=$1
S3_BKT=$2
LOG_PATTERN="^.*_log[0-9]+\.log$"
ARCHIVE_NAME="backup_$(date +%Y%m%d)_$$.tar.gz" # Using PID i.e $$ along with timestamp...
# Archive all local files....
# Fixed variable name from $LOCAL_DIR to $LOCAL_PATH
echo "Starting archiver (PID: $$) for $LOCAL_PATH"
BACKUP_FILES=""
for file in "$LOCAL_PATH"/*;
do
file_name=$(basename "$file") # Extracts filename from path
# Corrected: Added $ to LOG_PATTERN for variable expansion
if [[ $file_name =~ $LOG_PATTERN ]];
then
BACKUP_FILES+="$file " # space to seprate files...
fi
done
# Fixed: Added space after [[ for syntax and set exit to 1 for failure
[[ -z "$BACKUP_FILES" ]] && echo "No files Log Files Found!! Exiting..." && exit 1
# Compress into .tar.gz
echo "COMPRESSING Logs into << $ARCHIVE_NAME >>"
tar -czf "$ARCHIVE_NAME" $BACKUP_FILES # Connects command to data stream
# While Loop for Reliable Upload
# Logic: Try to copy to S3; exit code 0 stops the loop
# Fixed: Corrected variable name from $S2_BKT to $S3_BKT
echo "Uploading to s3://$S3_BKT/"
# Logic: While the exit code of the command is NOT 0 (success)
while ! aws s3 cp "$ARCHIVE_NAME" "s3://$S3_BKT/" > /dev/null 2>&1
do
echo "Network error. Retrying in 5 seconds..."
sleep 5
done
# Only remove local archive if the previous S3 command succeeded
[ $? -eq 0 ] && echo "Upload Verified!" && rm "$ARCHIVE_NAME" || echo "Critical: Upload failed."
echo "Process complete. Total arguments processed: $#"

Bash Options

Variable

Conditionals and Case Statements

Loops

Data Streams & Pipes

Functions

TASKS

AWK

SED

CRAP!!