Bash arrays are zero-indexed. Unlike many languages, array elements can contain any string, including those with spaces.
# Declaration
fruits=("apple" "banana" "cherry" "date")
# Access elements
echo "${fruits[0]}" # apple
echo "${fruits[2]}" # cherry
echo "${fruits[-1]}" # date (last element — Bash 4.2+)
# All elements (quote both ways work slightly differently)
echo "${fruits[@]}" # apple banana cherry date
echo "${fruits[*]}" # apple banana cherry date
# Number of elements
echo "${#fruits[@]}" # 4
# Array slice (offset, length)
echo "${fruits[@]:1:2}" # banana cherry (start at index 1, take 2)
# All indices
echo "${!fruits[@]}" # 0 1 2 3# Append an element
fruits+=("elderberry")
fruits[5]="fig"
# Delete an element (the index gap remains)
unset fruits[1]
echo "${fruits[@]}" # apple cherry date elderberry fig
# Re-index after deletion
fruits=("${fruits[@]}") # Re-pack into a contiguous array
# Delete the entire array
unset fruits# Iterate over elements (always quote "$@" style)
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
# Iterate with index
for i in "${!fruits[@]}"; do
echo "fruits[$i] = ${fruits[$i]}"
doneBash 4.0+ supports key-value associative arrays. Declare them with -A:
# Declaration (requires Bash 4+)
declare -A config
# Assign key-value pairs
config[host]="localhost"
config[port]="5432"
config[database]="mydb"
config[user]="admin"
# Or declare and populate at once
declare -A http_codes=(
[200]="OK"
[301]="Moved Permanently"
[404]="Not Found"
[500]="Internal Server Error"
)
# Access by key
echo "${config[host]}" # localhost
echo "${http_codes[404]}" # Not Found
# All keys
echo "${!config[@]}" # host port database user
# All values
echo "${config[@]}" # localhost 5432 mydb admin
# Check if key exists
[[ -v config[host] ]] && echo "host key exists"
# Iterate key-value pairs
for key in "${!config[@]}"; do
printf " %-12s = %s\n" "$key" "${config[$key]}"
doneBash has powerful built-in string manipulation — no awk or sed needed for simple operations.
str="Hello, World!"
echo "${#str}" # 13str="Hello, World!"
echo "${str:7}" # World! (from index 7 to end)
echo "${str:7:5}" # World (from index 7, length 5)
echo "${str: -6}" # orld! (last 6 chars — note the space before -)path="/usr/local/bin/script.sh"
# Replace first match
echo "${path/local/global}" # /usr/global/bin/script.sh
# Replace ALL matches
text="the cat sat on the mat"
echo "${text//the/a}" # a cat sat on a mat
# Delete pattern (replace with nothing)
echo "${path//.sh/}" # /usr/local/bin/scriptstr="Hello World"
echo "${str^^}" # HELLO WORLD (all uppercase)
echo "${str,,}" # hello world (all lowercase)
echo "${str^}" # Hello World (capitalize first char)
echo "${str,}" # hello World (lowercase first char)filename="report_2026_final.tar.gz"
# Strip shortest prefix matching pattern (#)
echo "${filename#*_}" # 2026_final.tar.gz
# Strip longest prefix matching pattern (##)
echo "${filename##*_}" # final.tar.gz
# Strip shortest suffix matching pattern (%)
echo "${filename%.*}" # report_2026_final.tar
# Strip longest suffix matching pattern (%%)
echo "${filename%%.*}" # report_2026_final
# Practical: get filename without extension
file="archive.tar.gz"
base="${file%%.*}" # archive
ext="${file#*.}" # tar.gzIFS controls how Bash splits words. Temporarily changing it lets you split strings on custom delimiters:
# Split a CSV line into an array
IFS=',' read -ra fields <<< "alice,30,engineer,london"
echo "${fields[0]}" # alice
echo "${fields[2]}" # engineer
# Join array elements with a custom separator
arr=("apple" "banana" "cherry")
(IFS=','; echo "${arr[*]}") # apple,banana,cherryarr=(a b c) # Create
echo "${arr[0]}" # Access
echo "${#arr[@]}" # Length
arr+=(d) # Append
for x in "${arr[@]}"; do ...; done # Iteratedeclare -A map
map[key]="value" # Set
echo "${map[key]}" # Get
echo "${!map[@]}" # All keys${#str} # Length
${str:N:L} # Substring
${str/old/new} # Replace first
${str//old/new} # Replace all
${str^^} # Uppercase
${str,,} # Lowercase
${str#prefix} # Strip prefix (shortest)
${str%suffix} # Strip suffix (shortest)