Introduction to Bash Arrays
Arrays in Bash allow you to store multiple values in a single variable. They are essential for managing collections of data, processing lists of items, and creating more sophisticated scripts. Bash supports both indexed arrays (with numeric indices) and associative arrays (with string keys, in Bash 4+).
Key Concepts
- Indexed Arrays: Traditional arrays with numeric indices (starting at 0)
- Associative Arrays: Arrays with named keys (like dictionaries/maps)
- Dynamic Sizing: Arrays grow and shrink automatically
- Sparse Arrays: Indices don't need to be contiguous
- Reference vs Copy: Array variables hold references to data
1. Creating Arrays
Indexed Arrays
#!/bin/bash # Method 1: Explicit declaration declare -a fruits fruits[0]="apple" fruits[1]="banana" fruits[2]="cherry" # Method 2: Direct assignment colors=("red" "green" "blue" "yellow") # Method 3: Individual assignments numbers[0]=10 numbers[1]=20 numbers[3]=40 # Sparse array (index 2 is empty) # Method 4: Using compound assignment files=($(ls)) # Creates array from command output # Method 5: Reading from file mapfile -t lines < file.txt # Read lines into array # Method 6: Using eval (caution!) eval "data=($(cat config.txt))" # Method 7: Empty array empty=() # Method 8: Array with mixed types (everything is string) mixed=(42 "hello" 3.14 "world") # Examples echo "Fruits: ${fruits[@]}" echo "Colors: ${colors[@]}" echo "Numbers: ${numbers[@]}" Associative Arrays (Bash 4+)
#!/bin/bash # Declare associative array declare -A user # Add key-value pairs user[name]="John Doe" user[age]=30 user[city]="New York" user[email]="[email protected]" # Alternative declaration and initialization declare -A capitals=( ["USA"]="Washington" ["UK"]="London" ["France"]="Paris" ["Japan"]="Tokyo" ) # Initialize with values declare -A scores=( ["Alice"]=95 ["Bob"]=87 ["Charlie"]=92 ) # Examples echo "User name: ${user[name]}" echo "Capital of France: ${capitals[France]}" echo "Alice's score: ${scores[Alice]}"
2. Accessing Array Elements
Basic Access
#!/bin/bash colors=("red" "green" "blue" "yellow") # Access by index echo "First color: ${colors[0]}" # red echo "Second color: ${colors[1]}" # green echo "Last color: ${colors[3]}" # yellow # Access all elements echo "All colors: ${colors[@]}" # red green blue yellow echo "All colors: ${colors[*]}" # red green blue yellow # Access with index variable i=2 echo "Color at index $i: ${colors[$i]}" # blue # Access last element (Bash 4.3+) echo "Last element: ${colors[-1]}" # yellow echo "Second last: ${colors[-2]}" # blue # Access range of elements (Bash 4+) echo "Elements 1-2: ${colors[@]:1:2}" # green blue # Access from offset to end echo "From index 2: ${colors[@]:2}" # blue yellow # Associative array access declare -A capitals=(["USA"]="Washington" ["UK"]="London") echo "USA capital: ${capitals[USA]}" echo "All capitals: ${capitals[@]}" echo "All countries: ${!capitals[@]}" Safe Access with Default Values
#!/bin/bash # Using default value if index doesn't exist colors=("red" "green" "blue") # Safe access with default echo "${colors[5]:-"unknown"}" # unknown (no error) echo "${colors[1]:-"unknown"}" # green # Using conditional if [ -n "${colors[5]+x}" ]; then echo "Element exists: ${colors[5]}" else echo "Element does not exist" fi # Check if index exists if [ ${#colors[@]} -gt 5 ]; then echo "Index 5 exists" fi # Get value or set default value=${colors[5]:-"default"} echo "Value: $value" # default 3. Array Operations
Getting Array Information
#!/bin/bash colors=("red" "green" "blue" "yellow") # Array length echo "Number of elements: ${#colors[@]}" # 4 echo "Number of elements: ${#colors[*]}" # 4 # Length of specific element echo "Length of first element: ${#colors[0]}" # 3 (length of "red") # All indices echo "Indices: ${!colors[@]}" # 0 1 2 3 # First element echo "First element: ${colors[0]}" # Last element echo "Last element: ${colors[-1]}" # Check if array is empty if [ ${#colors[@]} -eq 0 ]; then echo "Array is empty" else echo "Array has ${#colors[@]} elements" fi # Associative array keys declare -A capitals=(["USA"]="DC" ["UK"]="London") echo "Keys: ${!capitals[@]}" # USA UK echo "Values: ${capitals[@]}" # DC London Adding Elements
#!/bin/bash # Initialize empty array fruits=() # Add elements one by one fruits+=("apple") fruits+=("banana") fruits+=("cherry") echo "Fruits: ${fruits[@]}" # apple banana cherry # Add multiple elements fruits+=("date" "elderberry" "fig") echo "More fruits: ${fruits[@]}" # Add at specific index fruits[10]="kiwi" # Creates sparse array echo "All fruits: ${fruits[@]}" echo "Indices: ${!fruits[@]}" # Append to existing element fruits[0]="${fruits[0]}_suffix" echo "Modified: ${fruits[0]}" # apple_suffix # Add to front (shift all elements) fruits=("new_front" "${fruits[@]}") echo "With front: ${fruits[@]}" # Insert at specific position pos=3 fruits=("${fruits[@]:0:$pos}" "inserted" "${fruits[@]:$pos}") echo "After insert: ${fruits[@]}" # Add multiple elements from command files=() while IFS= read -r file; do files+=("$file") done < <(ls *.txt 2>/dev/null) Removing Elements
#!/bin/bash colors=("red" "green" "blue" "yellow" "purple" "orange") # Remove by index unset colors[1] # Removes "green" echo "After unset index 1: ${colors[@]}" echo "Indices: ${!colors[@]}" # 0 2 3 4 5 # Remove by value (find index first) value="blue" for i in "${!colors[@]}"; do if [ "${colors[$i]}" = "$value" ]; then unset colors[$i] echo "Removed $value at index $i" break fi done # Remove multiple elements unset colors[2] colors[3] # Remove specific indices # Remove first element colors=("${colors[@]:1}") echo "After removing first: ${colors[@]}" # Remove last element colors=("${colors[@]:0:${#colors[@]}-1}") echo "After removing last: ${colors[@]}" # Remove range of elements start=1 count=2 colors=("${colors[@]:0:$start}" "${colors[@]:$((start+count))}") echo "After removing range: ${colors[@]}" # Clear entire array unset colors echo "Array cleared: ${colors[@]:-}" # Remove by pattern numbers=(1 2 3 4 5 6 7 8 9 10) for i in "${!numbers[@]}"; do if (( numbers[i] % 2 == 0 )); then unset numbers[i] fi done echo "Odd numbers: ${numbers[@]}" # 1 3 5 7 9 Modifying Elements
#!/bin/bash words=("hello" "world" "bash" "scripting") # Update by index words[1]="universe" echo "After update: ${words[@]}" # hello universe bash scripting # Apply transformation to all elements for i in "${!words[@]}"; do words[$i]="${words[$i]^^}" # Uppercase done echo "Uppercase: ${words[@]}" # HELLO UNIVERSE BASH SCRIPTING # Replace substring in all elements for i in "${!words[@]}"; do words[$i]="${words[$i]/HELLO/HI}" done echo "After replacement: ${words[@]}" # HI UNIVERSE BASH SCRIPTING # Append to each element for i in "${!words[@]}"; do words[$i]="${words[$i]}_suffix" done echo "With suffix: ${words[@]}" # Conditional modification numbers=(1 2 3 4 5 6 7 8 9 10) for i in "${!numbers[@]}"; do if (( numbers[i] % 2 == 0 )); then numbers[i]=$((numbers[i] * 2)) fi done echo "Modified numbers: ${numbers[@]}" 4. Iterating Over Arrays
Basic Loops
#!/bin/bash fruits=("apple" "banana" "cherry" "date") # Loop over elements (for loop) echo "Fruits:" for fruit in "${fruits[@]}"; do echo " - $fruit" done # Loop over indices echo -e "\nFruits with indices:" for i in "${!fruits[@]}"; do echo " [$i] = ${fruits[$i]}" done # C-style for loop echo -e "\nC-style loop:" for (( i=0; i<${#fruits[@]}; i++ )); do echo " fruits[$i] = ${fruits[$i]}" done # While loop with index echo -e "\nWhile loop:" i=0 while [ $i -lt ${#fruits[@]} ]; do echo " fruits[$i] = ${fruits[$i]}" ((i++)) done # Until loop echo -e "\nUntil loop:" i=0 until [ $i -ge ${#fruits[@]} ]; do echo " fruits[$i] = ${fruits[$i]}" ((i++)) done Advanced Iteration
#!/bin/bash # Loop over sparse array sparse=([0]="first" [2]="second" [5]="third") for i in "${!sparse[@]}"; do echo "Index $i: ${sparse[$i]}" done # Loop with enumeration colors=("red" "green" "blue" "yellow") for i in "${!colors[@]}"; do printf "%2d: %s\n" "$i" "${colors[$i]}" done # Process in chunks numbers=({1..20}) chunk_size=5 for (( i=0; i<${#numbers[@]}; i+=chunk_size )); do chunk=("${numbers[@]:i:chunk_size}") echo "Processing chunk starting at $i: ${chunk[@]}" done # Reverse iteration for (( i=${#colors[@]}-1; i>=0; i-- )); do echo "colors[$i] = ${colors[$i]}" done # Nested loops (2D array simulation) matrix=( "1 2 3" "4 5 6" "7 8 9" ) for row in "${matrix[@]}"; do for val in $row; do echo -n "$val " done echo done Functional Programming Style
#!/bin/bash # Map (transform each element) map() { local arr=("${!1}") local result=() for item in "${arr[@]}"; do result+=($($2 "$item")) done echo "${result[@]}" } # Filter elements filter() { local arr=("${!1}") local result=() for item in "${arr[@]}"; do if $2 "$item"; then result+=("$item") fi done echo "${result[@]}" } # Reduce (fold) reduce() { local arr=("${!1}") local acc=$2 local op=$3 for item in "${arr[@]}"; do acc=$((acc $op item)) done echo $acc } # Usage nums=(1 2 3 4 5) # Square function square() { echo $(( $1 * $1 )); } # Is even function is_even() { (( $1 % 2 == 0 )); } squared=$(map nums[@] square) echo "Squared: $squared" evens=$(filter nums[@] is_even) echo "Evens: $evens" sum=$(reduce nums[@] 0 +) echo "Sum: $sum" 5. Array Manipulation Techniques
Sorting Arrays
#!/bin/bash # Sort numerically numbers=(5 2 8 1 9 3 7 4 6) sorted=($(for n in "${numbers[@]}"; do echo $n; done | sort -n)) echo "Sorted numbers: ${sorted[@]}" # Sort reverse numerically sorted=($(for n in "${numbers[@]}"; do echo $n; done | sort -nr)) echo "Reverse sorted: ${sorted[@]}" # Sort alphabetically fruits=("banana" "apple" "cherry" "date") sorted=($(for f in "${fruits[@]}"; do echo $f; done | sort)) echo "Sorted fruits: ${sorted[@]}" # Sort by string length sorted=($(for f in "${fruits[@]}"; do echo "$f"; done | awk '{ print length, $0 }' | sort -n | cut -d' ' -f2-)) echo "Sorted by length: ${sorted[@]}" # Sort with custom comparator sort_by_last_char() { for item in "$@"; do echo "${item: -1}:$item" done | sort | cut -d: -f2- } words=("hello" "world" "bash" "scripting") sorted=($(sort_by_last_char "${words[@]}")) echo "Sorted by last char: ${sorted[@]}" Unique Elements
#!/bin/bash # Get unique elements numbers=(1 2 2 3 3 3 4 4 4 4 5) unique=($(printf "%s\n" "${numbers[@]}" | sort -u)) echo "Unique: ${unique[@]}" # Count occurrences declare -A count for num in "${numbers[@]}"; do ((count[$num]++)) done for num in "${!count[@]}"; do echo "$num appears ${count[$num]} times" done # Remove duplicates (preserve order) unique=() for item in "${numbers[@]}"; do found=0 for u in "${unique[@]}"; do if [ "$u" = "$item" ]; then found=1 break fi done if [ $found -eq 0 ]; then unique+=("$item") fi done echo "Unique (order preserved): ${unique[@]}" Array Slicing and Joining
#!/bin/bash colors=("red" "green" "blue" "yellow" "purple" "orange") # Slice array slice=("${colors[@]:1:3}") # Elements 1-3 echo "Slice (1-3): ${slice[@]}" # green blue yellow # All elements from index 2 from_index2=("${colors[@]:2}") echo "From index 2: ${from_index2[@]}" # blue yellow purple orange # First 3 elements first3=("${colors[@]::3}") echo "First 3: ${first3[@]}" # red green blue # Last 3 elements last3=("${colors[@]: -3}") echo "Last 3: ${last3[@]}" # yellow purple orange # Join array elements with delimiter join() { local IFS="$1" shift echo "$*" } joined=$(join , "${colors[@]}") echo "Joined with comma: $joined" # red,green,blue,yellow,purple,orange joined=$(join " | " "${colors[@]}") echo "Joined with pipe: $joined" # red | green | blue | yellow | purple | orange # Split string into array string="apple,banana,cherry" IFS=',' read -ra fruits <<< "$string" echo "Split array: ${fruits[@]}" Array Reversal and Rotation
#!/bin/bash # Reverse array reverse() { local arr=("${!1}") local result=() for (( i=${#arr[@]}-1; i>=0; i-- )); do result+=("${arr[$i]}") done echo "${result[@]}" } colors=("red" "green" "blue" "yellow") reversed=($(reverse colors[@])) echo "Reversed: ${reversed[@]}" # yellow blue green red # Rotate left rotate_left() { local arr=("${!1}") local n=$2 n=$((n % ${#arr[@]})) echo "${arr[@]:n}" "${arr[@]:0:n}" } rotated=($(rotate_left colors[@] 2)) echo "Rotated left by 2: ${rotated[@]}" # blue yellow red green # Rotate right rotate_right() { local arr=("${!1}") local n=$2 n=$((n % ${#arr[@]})) echo "${arr[@]: -n}" "${arr[@]:0:${#arr[@]}-n}" } rotated=($(rotate_right colors[@] 2)) echo "Rotated right by 2: ${rotated[@]}" # green blue red yellow # Shuffle array shuffle() { local arr=("${!1}") local result=() local indices=(${!arr[@]}) while [ ${#indices[@]} -gt 0 ]; do local rand=$((RANDOM % ${#indices[@]})) local idx=${indices[$rand]} result+=("${arr[$idx]}") unset indices[$rand] indices=("${indices[@]}") done echo "${result[@]}" } shuffled=($(shuffle colors[@])) echo "Shuffled: ${shuffled[@]}" 6. Multidimensional Arrays
Simulating 2D Arrays
#!/bin/bash # Method 1: Using arrays of strings matrix=( "1 2 3" "4 5 6" "7 8 9" ) # Access element get_element() { local row=$1 local col=$2 local line="${matrix[$row]}" local elements=($line) echo "${elements[$col]}" } echo "Element at (1,1): $(get_element 1 1)" # 5 # Print matrix print_matrix() { for row in "${matrix[@]}"; do echo "$row" done } echo -e "\nMatrix:" print_matrix # Method 2: Using associative arrays declare -A matrix2 matrix2[0,0]=1 matrix2[0,1]=2 matrix2[0,2]=3 matrix2[1,0]=4 matrix2[1,1]=5 matrix2[1,2]=6 matrix2[2,0]=7 matrix2[2,1]=8 matrix2[2,2]=9 echo -e "\nMatrix2[1,1] = ${matrix2[1,1]}" # 5 # Method 3: Using arrays of arrays (Bash 4+) declare -A matrix3 matrix3["row0"]="1 2 3" matrix3["row1"]="4 5 6" matrix3["row2"]="7 8 9" # Method 4: Using eval for dynamic access rows=3 cols=3 declare -A matrix4 for (( i=0; i<rows; i++ )); do for (( j=0; j<cols; j++ )); do matrix4[$i,$j]=$((i*cols + j + 1)) done done echo "Matrix4[2,2] = ${matrix4[2,2]}" # 9 Matrix Operations
#!/bin/bash # Matrix addition matrix_add() { local rows=$1 local cols=$2 shift 2 local -a a=("${!1}") local -a b=("${!2}") local -a result=() for (( i=0; i<rows; i++ )); do for (( j=0; j<cols; j++ )); do idx=$((i*cols + j)) result[idx]=$((a[idx] + b[idx])) done done echo "${result[@]}" } # Matrix multiplication matrix_multiply() { local rows1=$1 local cols1=$2 local rows2=$3 local cols2=$4 shift 4 local -a a=("${!1}") local -a b=("${!2}") local -a result=() if [ $cols1 -ne $rows2 ]; then echo "Incompatible dimensions" return 1 fi for (( i=0; i<rows1; i++ )); do for (( j=0; j<cols2; j++ )); do sum=0 for (( k=0; k<cols1; k++ )); do sum=$((sum + a[i*cols1 + k] * b[k*cols2 + j])) done result[i*cols2 + j]=$sum done done echo "${result[@]}" } # Example usage a=(1 2 3 4) # 2x2 matrix: [1 2; 3 4] b=(5 6 7 8) # 2x2 matrix: [5 6; 7 8] result=($(matrix_add 2 2 a[@] b[@])) echo "Matrix addition: ${result[@]}" # [6 8; 10 12] result=($(matrix_multiply 2 2 2 2 a[@] b[@])) echo "Matrix multiplication: ${result[@]}" # [19 22; 43 50] 7. Arrays in Functions
Passing Arrays to Functions
#!/bin/bash # Arrays can't be passed directly - pass by name print_array() { local -n arr=$1 # Nameref (Bash 4.3+) echo "Array contents: ${arr[@]}" } # Alternative: pass by value (requires copying) process_array() { local arr=("${!1}") echo "Processing: ${arr[@]}" arr[0]="modified" echo "Modified: ${arr[@]}" } # Return array from function get_numbers() { local result=(1 2 3 4 5) echo "${result[@]}" # Return as string } # Usage my_array=("apple" "banana" "cherry") # Pass by name (modifiable) print_array my_array # Pass by value (copy) process_array my_array[@] # Original unchanged echo "Original: ${my_array[@]}" # Still "apple banana cherry" # Capture returned array numbers=($(get_numbers)) echo "Numbers: ${numbers[@]}" Array Utility Functions
#!/bin/bash # Check if array contains element contains() { local -n arr=$1 local value=$2 for item in "${arr[@]}"; do if [ "$item" = "$value" ]; then return 0 fi done return 1 } # Get index of element index_of() { local -n arr=$1 local value=$2 for i in "${!arr[@]}"; do if [ "${arr[$i]}" = "$value" ]; then echo $i return 0 fi done echo -1 return 1 } # Remove element by value remove() { local -n arr=$1 local value=$2 local new_arr=() for item in "${arr[@]}"; do if [ "$item" != "$value" ]; then new_arr+=("$item") fi done arr=("${new_arr[@]}") } # Union of two arrays array_union() { local -n arr1=$1 local -n arr2=$2 local result=() local seen=() for item in "${arr1[@]}" "${arr2[@]}"; do if [[ ! " ${seen[@]} " =~ " ${item} " ]]; then result+=("$item") seen+=("$item") fi done echo "${result[@]}" } # Intersection of two arrays array_intersection() { local -n arr1=$1 local -n arr2=$2 local result=() for item1 in "${arr1[@]}"; do for item2 in "${arr2[@]}"; do if [ "$item1" = "$item2" ]; then result+=("$item1") break fi done done echo "${result[@]}" } # Usage examples fruits=("apple" "banana" "cherry" "date") if contains fruits "banana"; then echo "Contains banana" fi idx=$(index_of fruits "cherry") echo "Index of cherry: $idx" remove fruits "banana" echo "After removal: ${fruits[@]}" a=(1 2 3 4) b=(3 4 5 6) union=($(array_union a b)) echo "Union: ${union[@]}" intersection=($(array_intersection a b)) echo "Intersection: ${intersection[@]}" 8. Practical Examples
CSV Parser
#!/bin/bash # Parse CSV file into array of records parse_csv() { local file=$1 local -n data=$2 local line_num=0 while IFS= read -r line; do # Skip empty lines [ -z "$line" ] && continue # Parse CSV (simple version - doesn't handle quoted commas) IFS=',' read -ra fields <<< "$line" # Store as "line_num:field_num=value" for i in "${!fields[@]}"; do data["$line_num,$i"]="${fields[$i]}" done ((line_num++)) done < "$file" echo "Parsed $line_num lines" } # Display CSV data display_csv() { local -n data=$1 local max_cols=0 # Find max columns for key in "${!data[@]}"; do col="${key#*,}" [ "$col" -gt "$max_cols" ] && max_cols=$col done # Print header for (( c=0; c<=max_cols; c++ )); do printf "%-15s" "Column $c" done echo # Print data local row=0 while true; do found=0 line="" for (( c=0; c<=max_cols; c++ )); do value="${data[$row,$c]}" if [ -n "$value" ]; then found=1 printf "%-15s" "$value" else printf "%-15s" "" fi done echo [ $found -eq 0 ] && break ((row++)) done } # Create sample CSV cat > sample.csv << EOF Name,Age,City John,30,New York Jane,25,Los Angeles Bob,35,Chicago EOF # Parse and display declare -A csv_data parse_csv sample.csv csv_data display_csv csv_data rm sample.csv Configuration File Parser
#!/bin/bash # Parse config file into associative array parse_config() { local file=$1 local -n config=$2 while IFS= read -r line; do # Skip comments and empty lines [[ "$line" =~ ^[[:space:]]*# ]] && continue [ -z "$line" ] && continue # Parse key=value if [[ "$line" =~ ^[[:space:]]*([^=]+)[[:space:]]*=[[:space:]]*(.*)$ ]]; then key="${BASH_REMATCH[1]}" value="${BASH_REMATCH[2]}" # Remove trailing comments value="${value%%#*}" # Trim whitespace key=$(echo "$key" | xargs) value=$(echo "$value" | xargs) config["$key"]="$value" fi done < "$file" } # Save config to file save_config() { local -n config=$1 local file=$2 { echo "# Configuration file generated $(date)" for key in "${!config[@]}"; do echo "$key = ${config[$key]}" done } > "$file" } # Create sample config cat > app.conf << EOF # Application configuration database_host = localhost database_port = 5432 app_name = MyApp debug_mode = true max_users = 100 EOF # Parse config declare -A config parse_config app.conf config # Display config echo "Configuration:" for key in "${!config[@]}"; do echo " $key = ${config[$key]}" done # Modify and save config["debug_mode"]="false" config["max_users"]=200 save_config config app_new.conf echo -e "\nUpdated config saved to app_new.conf" Directory Tree Scanner
#!/bin/bash # Scan directory recursively into arrays declare -a files declare -a dirs declare -A file_sizes scan_directory() { local dir=$1 local prefix=$2 for item in "$dir"/*; do [ -e "$item" ] || continue if [ -f "$item" ]; then files+=("$prefix$(basename "$item")") file_sizes["$prefix$(basename "$item")"]=$(stat -c%s "$item" 2>/dev/null || stat -f%z "$item" 2>/dev/null) elif [ -d "$item" ]; then local name=$(basename "$item") dirs+=("$prefix$name") scan_directory "$item" "$prefix$name/" fi done } # Display tree display_tree() { echo "Directories:" printf ' %s\n' "${dirs[@]}" echo -e "\nFiles:" for file in "${files[@]}"; do size=${file_sizes[$file]} printf " %s (%d bytes)\n" "$file" "$size" done echo -e "\nStatistics:" echo " Total directories: ${#dirs[@]}" echo " Total files: ${#files[@]}" total_size=0 for size in "${file_sizes[@]}"; do total_size=$((total_size + size)) done echo " Total size: $total_size bytes" } # Scan current directory scan_directory "." "" display_tree Command History Manager
#!/bin/bash # Simple command history manager declare -a history declare -A command_count # Add command to history add_command() { local cmd=$1 history+=("$(date '+%Y-%m-%d %H:%M:%S'): $cmd") ((command_count["$cmd"]++)) } # Show recent commands recent() { local n=${1:-10} local start=$(( ${#history[@]} > n ? ${#history[@]} - n : 0 )) echo "Recent commands:" for (( i=start; i<${#history[@]}; i++ )); do echo " $((i+1)). ${history[$i]}" done } # Show command statistics stats() { echo "Command statistics:" for cmd in "${!command_count[@]}"; do echo " $cmd: ${command_count[$cmd]} times" done | sort -rn -k3 } # Search history search_history() { local pattern=$1 echo "Search results for '$pattern':" for entry in "${history[@]}"; do if [[ "$entry" =~ $pattern ]]; then echo " $entry" fi done } # Example usage add_command "ls -la" add_command "cd /tmp" add_command "grep pattern file.txt" add_command "ls -la" add_command "ps aux" recent echo -e "\n" stats echo -e "\n" search_history "ls" 9. Advanced Array Techniques
Using Arrays with Process Substitution
#!/bin/bash # Read command output directly into array mapfile -t users < <(cut -d: -f1 /etc/passwd | head -5) echo "First 5 users: ${users[@]}" # Process array with while loop printf "%s\n" "${users[@]}" | while read user; do echo "Processing user: $user" done # Use process substitution with arrays diff <(printf "%s\n" "${array1[@]}" | sort) <(printf "%s\n" "${array2[@]}" | sort) # Create array from find results mapfile -t txt_files < <(find . -name "*.txt" -type f) echo "Found ${#txt_files[@]} text files" # Parallel processing with arrays process_chunk() { local chunk=("${!1}") for item in "${chunk[@]}"; do echo "Processing: $item" # Do work here done } # Split array into chunks and process in parallel chunk_size=10 for (( i=0; i<${#files[@]}; i+=chunk_size )); do chunk=("${files[@]:i:chunk_size}") process_chunk chunk[@] & done wait Array Index Manipulation
#!/bin/bash # Reindex array (remove gaps) reindex() { local -n arr=$1 arr=("${arr[@]}") } # Shift indices shift_indices() { local -n arr=$1 local shift=$2 local new_arr=() for i in "${!arr[@]}"; do new_arr[$((i + shift))]="${arr[$i]}" done arr=("${new_arr[@]}") } # Compact array (remove empty elements) compact() { local -n arr=$1 local new_arr=() for item in "${arr[@]}"; do [ -n "$item" ] && new_arr+=("$item") done arr=("${new_arr[@]}") } # Example sparse=( [0]="a" [2]="b" [5]="c" ) echo "Sparse: ${sparse[@]}" echo "Indices: ${!sparse[@]}" reindex sparse echo "Reindexed: ${sparse[@]}" echo "Indices: ${!sparse[@]}" shift_indices sparse 2 echo "Shifted: ${sparse[@]}" echo "Indices: ${!sparse[@]}" mixed=("a" "" "b" "" "c" "") compact mixed echo "Compacted: ${mixed[@]}" Array References (Bash 4.3+)
#!/bin/bash # Using namerefs for array references process_arrays() { local -n arr1=$1 local -n arr2=$2 echo "Array1: ${arr1[@]}" echo "Array2: ${arr2[@]}" # Modify through reference arr1[0]="modified" } # Array of array names arrays=(fruits colors numbers) # Declare some arrays fruits=("apple" "banana" "cherry") colors=("red" "green" "blue") numbers=(1 2 3 4 5) # Process each array for arr_name in "${arrays[@]}"; do local -n arr=$arr_name echo "Processing $arr_name: ${arr[@]}" done # Call function with references process_arrays fruits colors echo "Fruits after: ${fruits[@]}" # First element modified 10. Best Practices and Tips
Common Patterns
#!/bin/bash # 1. Always quote array expansions fruits=("apple" "banana with space" "cherry") # Good - preserves elements with spaces for fruit in "${fruits[@]}"; do echo "Fruit: $fruit" done # Bad - splits on spaces for fruit in ${fruits[@]}; do echo "Fruit: $fruit" # "banana" and "with" and "space" become separate done # 2. Check if array is empty if [ ${#array[@]} -eq 0 ]; then echo "Array is empty" fi # 3. Default array empty_array=() default_array=(${empty_array[@]:-${default_values[@]}}) # 4. Safe array copying copy_array() { local -n src=$1 local -n dst=$2 dst=("${src[@]}") } # 5. Array intersection using associative array array_intersect() { local -n arr1=$1 local -n arr2=$2 local -A temp local result=() for item in "${arr2[@]}"; do temp["$item"]=1 done for item in "${arr1[@]}"; do [ -n "${temp[$item]}" ] && result+=("$item") done echo "${result[@]}" } Performance Considerations
#!/bin/bash # Large array operations can be slow # Use associative arrays for lookups instead of linear search # Bad - linear search O(n) slow_lookup() { local -n arr=$1 local value=$2 for item in "${arr[@]}"; do [ "$item" = "$value" ] && return 0 done return 1 } # Good - hash lookup O(1) declare -A fast_lookup for item in "${large_array[@]}"; do fast_lookup["$item"]=1 done if [ -n "${fast_lookup[$value]}" ]; then echo "Found" fi # Use mapfile for reading files instead of while-read # Slow while read line; do arr+=("$line") done < file.txt # Fast mapfile -t arr < file.txt # Use local variables in functions for speed slow_function() { arr=("${!1}") } fast_function() { local -n arr=$1 # Reference is faster than copy } Debugging Arrays
#!/bin/bash # Print array with indices print_array() { local -n arr=$1 echo "Array: $1" for i in "${!arr[@]}"; do echo " [$i] = '${arr[$i]}'" done } # Debug function debug_array() { if [ "${DEBUG:-0}" -eq 1 ]; then local -n arr=$1 echo "DEBUG: $1 (size=${#arr[@]})" for i in "${!arr[@]}"; do echo " [$i] = '${arr[$i]}'" done fi } # Validate array indices validate_indices() { local -n arr=$1 local expected=$2 for (( i=0; i<expected; i++ )); do if [ -z "${arr[$i]+x}" ]; then echo "Warning: Missing index $i" fi done } # Example DEBUG=1 my_array=("a" "b" "c") debug_array my_array 11. Command Summary and Cheat Sheet
Quick Reference
# Declaration declare -a arr # Indexed array declare -A assoc # Associative array arr=() # Empty array arr=(1 2 3) # Initialize with values assoc=([key1]=val1 [key2]=val2) # Initialize associative # Access ${arr[@]} # All values ${!arr[@]} # All indices ${#arr[@]} # Array length ${#arr[0]} # Length of element 0 ${arr[i]} # Element at index i ${arr[-1]} # Last element # Slicing ${arr[@]:start:count} # Slice array ${arr[@]:start} # From start to end ${arr[@]::count} # First count elements ${arr[@]: -count} # Last count elements # Modification arr+=("new") # Append element arr=("new" "${arr[@]}") # Prepend element arr[i]="value" # Set element unset arr[i] # Remove element arr=("${arr[@]}") # Reindex array # Operations for item in "${arr[@]}"; do # Iterate values for i in "${!arr[@]}"; do # Iterate indices printf "%s\n" "${arr[@]}" # Print each on new line # Copying new=("${old[@]}") # Copy array declare -n ref=arr # Create reference # Reading mapfile -t arr < file # Read file into array IFS=',' read -ra arr <<< "a,b,c" # Split string # Testing [ ${#arr[@]} -eq 0 ] # Check if empty [[ " ${arr[@]} " =~ " $value " ]] # Check if contains Conclusion
Key Takeaways
- Array Types: Indexed (numeric) and Associative (string keys)
- Dynamic: Arrays grow and shrink automatically
- Sparse: Indices don't need to be contiguous
- Passing: Arrays are passed by name, not by value
- Operations: Rich set of operations for manipulation
Best Practices Summary
| Operation | Recommended Method |
|---|---|
| Declaration | declare -a arr or arr=() |
| Iteration | for item in "${arr[@]}" |
| Length | ${#arr[@]} |
| Append | arr+=("new") |
| Remove | unset arr[i] |
| Copy | new=("${old[@]}") |
| Contains | Use associative array for lookups |
| Read file | mapfile -t arr < file |
| Pass to function | Use nameref (local -n) |
Common Pitfalls
# Pitfall 1: Not quoting array expansion for fruit in ${fruits[@]} # Wrong - splits on spaces for fruit in "${fruits[@]}" # Correct # Pitfall 2: Using [*] instead of [@] echo "${fruits[*]}" # All elements as one string echo "${fruits[@]}" # Separate elements # Pitfall 3: Forgetting indices are zero-based fruits[1]="apple" # This is actually the second element # Pitfall 4: Not checking if index exists value=${fruits[100]} # Returns empty string, no error # Pitfall 5: Modifying IFS incorrectly IFS=',' read -ra arr <<< "a,b,c" # Works # Remember to restore IFS or use local # Pitfall 6: Using array in scalar context echo $fruits # Only prints first element echo ${fruits[@]} # Correct - prints all # Pitfall 7: Array comparison [ "$arr1" = "$arr2" ] # Wrong - compares as strings # Compare elements manually or use diff Mastering Bash arrays is essential for writing efficient and powerful shell scripts. They provide a flexible way to manage collections of data and are fundamental to many advanced scripting techniques.
Complete Bash Exercises with Solutions
Practice Bash scripting with solved exercises to improve your command-line and scripting skills.
Link: https://macronepal.com/complete-bash-exercises-with-solutions/
Complete Guide to Bash Crontab Command
Learn how to schedule and automate tasks efficiently using the Bash crontab command.
Link: https://macronepal.com/complete-guide-to-bash-crontab-command-schedule-tasks/
Complete Guide to Bash Arrays
Understand how to create, manage, and use arrays in Bash scripting.
Link: https://macronepal.com/complete-guide-to-bash-arrays/
Bash Quiz: Test Your Knowledge
Test your Bash scripting knowledge with quizzes and practical questions.
Link: https://macronepal.com/bash-quiz-test-your-knowledge/
Complete Guide to Bash Functions
Learn how to write reusable Bash functions for cleaner and efficient scripts.
Link: https://macronepal.com/complete-guide-to-bash-functions/
Complete Guide to Bash Loops
Master loops in Bash for repetitive tasks and automation.
Link: https://macronepal.com/complete-guide-to-bash-loops/
Complete Guide to Bash If-Else Statements
Learn decision-making in Bash using conditional statements.
Link: https://macronepal.com/complete-guide-to-bash-ifelse-statements/
Complete Guide to Bash Operators
Explore arithmetic, logical, and comparison operators in Bash.
Link: https://macronepal.com/complete-guide-to-bash-operators/
Complete Guide to Bash Data Types
Understand variables and different data types used in Bash scripting.
Link: https://macronepal.com/complete-guide-to-bash-data-types/
Complete Guide to Bash Variables
Learn how to declare, assign, and use variables in Bash scripts.
Link: https://macronepal.com/complete-guide-to-bash-variables/
Complete Guide to Bash Scripting
A complete introduction to writing Bash scripts for automation.
Link: https://macronepal.com/complete-guide-to-bash-scripting/
Complete Guide to Basic Bash Syntax
Learn the fundamental syntax needed to write Bash commands and scripts.
Link: https://macronepal.com/complete-guide-to-basic-bash-syntax/
Complete Guide to Bash Chgrp Command
Learn how to change group ownership of files and directories.
Link: https://macronepal.com/complete-guide-to-bash-chgrp-command-change-group-ownership-2/
Complete Guide to Bash Chgrp Command (Alternate)
Another detailed explanation of Bash group ownership management.
Link: https://macronepal.com/complete-guide-to-bash-chgrp-command-change-group-ownership/
Complete Guide to Bash File Permissions and Ownership
Understand Linux file permissions and ownership management.
Link: https://macronepal.com/complete-guide-to-bash-file-permissions-and-ownership/
Complete Guide to Bash Chmod Command
Learn how to modify file permissions using chmod.
Link: https://macronepal.com/complete-guide-to-bash-chmod-command-change-file-permissions/
Complete Guide to Bash Chown Command
Change file ownership using the Bash chown command.
Link: https://macronepal.com/complete-guide-to-bash-chown-command-change-file-ownership/
Complete Guide to Bash SCP Command
Securely transfer files between systems using SCP.
Link: https://macronepal.com/complete-guide-to-bash-scp-command-secure-copy/
Complete Guide to Bash SSH Command
Learn secure remote access and server management using SSH.
Link: https://macronepal.com/complete-guide-to-bash-ssh-command/
Bash Wget Command Complete Guide
Download files from the internet using the wget command.
Link: https://macronepal.com/bash-wget-command-complete-guide-to-non-interactive-network-downloader/
