Skip to content

L_foreach

L_foreach is a powerful and flexible Bash function for iterating over the elements of one or more arrays. It provides a clean, readable alternative to complex for loops, especially when you need to process items in groups or iterate over associative arrays in a controlled manner.

It is used within a while loop, and on each iteration, it assigns values from the source array(s) to one or more variables. The loop continues as long as L_foreach can assign at least one variable.

Basic Usage: Iterating Over a Single Array

The simplest use case is iterating over a standard array and assigning each element to a single variable. The syntax requires you to specify the variable name(s), a colon separator :, and the array name(s).

#!/bin/bash
. L_lib.sh -s

# Define an array of strings
servers=("server-alpha" "server-beta" "server-gamma")

# Loop over each server
while L_foreach name : servers; do
  echo "Pinging server: $name"
  # ping -c 1 "$name"
done
Output:
Pinging server: server-alpha
Pinging server: server-beta
Pinging server: server-gamma

Processing Items in Groups (Tuples)

L_foreach can assign multiple variables on each iteration, allowing you to process an array in fixed-size chunks or "tuples".

# An array containing filenames and their corresponding sizes
files_data=("report.txt" "1024" "image.jpg" "4096" "archive.zip" "16384")

# Process the array in pairs
while L_foreach filename size : files_data; do
  echo "File '$filename' is $size bytes."
done
Output:
File 'report.txt' is 1024 bytes.
File 'image.jpg' is 4096 bytes.
File 'archive.zip' is 16384 bytes.
If the number of elements is not a perfect multiple of the variables, the last iteration will assign the remaining elements, and the leftover variables will be assigned an empty string. You can use the -e flag to accurately check if an element was actually present in the array versus just being empty.

Iterating Over Associative Arrays (-k)

Handling associative arrays (or "dictionaries") is a key feature. By default, the iteration order is not guaranteed.

# Define an associative array mapping services to ports
declare -A services=([http]=80 [ssh]=22 [smtp]=25)

# -k saves the key, and 'port' gets the value
while L_foreach -k service_name port : services; do
  echo "Service '$service_name' runs on port $port."
done
Example Output (order may vary):
Service 'http' runs on port 80.
Service 'ssh' runs on port 22.
Service 'smtp' runs on port 25.

Sorted Iteration (-s -r)

To iterate in a predictable order, use the -s flag to sort by the array keys.

declare -A services=([http]=80 [ssh]=22 [smtp]=25)

echo "--- Services sorted by name ---"
while L_foreach -s -k name port : services; do
  echo "Service: $name (Port: $port)"
done
Output:
--- Services sorted by name ---
Service: http (Port: 80)
Service: smtp (Port: 25)
Service: ssh (Port: 22)

Sorted Iteration by Value (-V) and Numeric Sort (-n)

If you want to sort by the values instead of the keys, use the -V flag. By default, the values are compared lexicographically. To compare them numerically, combine it with the -n flag. This is essential when dealing with port numbers, sizes, or other numeric data.

declare -A services=([http]=80 [ssh]=22 [https]=443 [smtp]=25 [myapp]=110)

echo "--- Services sorted by port number (numeric) ---"
while L_foreach -V -n -k name port : services; do
  echo "Service: $name (Port: $port)"
done
Output:
--- Services sorted by port number (numeric) ---
Service: ssh (Port: 22)
Service: smtp (Port: 25)
Service: http (Port: 80)
Service: myapp (Port: 110)
Service: https (Port: 443)

Without the -n flag, 110 would appear before 22 because it starts with the character 1.

Sorted Iteration by Keys (-s -n -r)

You can also sort keys numerically using -n or in reverse order using -r.

declare -A data=([2]=alpha [10]=beta [1]=gamma)

echo "--- Keys sorted numerically ---"
while L_foreach -s -n -k key val : data; do
  echo "Key $key: $val"
done
Output:
--- Keys sorted numerically ---
Key 1: gamma
Key 2: alpha
Key 10: beta

Combining Multiple Arrays

L_foreach can iterate over multiple arrays in parallel.

Horizontal Iteration (Default)

This is useful for processing consecutive lists of data.

local users=("alice" "bob")
local roles=("admin" "editor")

# The loop processes 'users', then continues with 'roles'
while L_foreach identity : users roles; do
  echo "Processing identity: $identity"
done
Output:
Processing identity: alice
Processing identity: bob
Processing identity: admin
Processing identity: editor

Vertical Iteration (-k)

When used with -k, L_foreach pairs elements from multiple arrays that share the same key. This is extremely powerful for correlating data between associative arrays.

declare -A user_roles=([alice]=admin [bob]=editor)
declare -A user_ids=([alice]=101 [bob]=102)

# Iterate using the keys from both arrays
while L_foreach -s -k name role id : user_roles user_ids; do
  echo "User: $name, ID: $id, Role: $role"
done
Output:
User: alice, ID: 101, Role: admin
User: bob, ID: 102, Role: editor

Tracking Loop State (-i -f -l)

You can track the loop's progress using special flags:

  • -i <var>: Stores the current loop index (starting from 0) in <var>.
  • -f <var>: Stores 1 in <var> during the first iteration, 0 otherwise.
  • -l <var>: Stores 1 in <var> during the last iteration, 0 otherwise.

items=("A" "B" "C")
separator=", "

while L_foreach -i idx -l is_last value : items; do
  echo -n "[$idx] $value"
  if (( ! is_last )); then
    echo -n "$separator"
  fi
done
echo # for a final newline
Output:
[0] A, [1] B, [2] C

Checking Element Existence in Sparse Arrays (-e)

When iterating over sparse arrays or combining multiple associative arrays where some keys might be missing, you can use the -e <var> flag to check if an element was actually present. This stores an array in <var> where each index corresponds to the assigned variable, containing 1 if the element existed, and an empty string otherwise.

local -A arr1=([0]=a [1]=b)
local -A arr2=([0]=c [2]=d)

# Iterate over all keys from both arrays, sorted by key
while L_foreach -s -k key -e exists -- val1 val2 : arr1 arr2; do
  echo -n "Key $key: "
  if [[ -n "${exists[0]}" ]]; then echo -n "arr1 has ${val1}, "; else echo -n "arr1 missing, "; fi
  if [[ -n "${exists[1]}" ]]; then echo "arr2 has ${val2}"; else echo "arr2 missing"; fi
done
Output:
Key 0: arr1 has a, arr2 has c
Key 1: arr1 has b, arr2 missing
Key 2: arr1 missing, arr2 has d

Advanced Array Assignment (-n)

If you want to assign elements into an array instead of separate variables, you can use the -n <num> flag. It dynamically repeats each specified variable name as an array index from 0 to num - 1.

data=("apple" "banana" "cherry" "date")

# -R 2 makes it assign to a[0] and a[1]
while L_foreach -R 2 a : data; do
  echo "Pair: ${a[0]} and ${a[1]:-none}"
done
Output:
Pair: apple and banana
Pair: cherry and date

Counting Assigned Variables (-c)

In scenarios where the number of elements might not evenly fill your requested variables, you can use -c <var> to store the exact count of variables that were assigned a value in the current iteration.

data=("A" "B" "C")

while L_foreach -c count a b : data; do
  echo "Assigned $count variables: a=$a, b=${b:-unset}"
done
Output:
Assigned 2 variables: a=A, b=B
Assigned 1 variables: a=C, b=unset

Generated documentation from source:

foreach

L_printf_v

Compatibility printf implementation for Bash<4.1

That properly sets the -v variable when it is an array. The variables are like printf -v $1 fmt args...

Arguments:

  • $1 variable to set
  • $2 fmt printf format string
  • $@ arg printf arguments....

L_foreach

Iterate over elements of an array by assigning it to variables.

Each loop the arguments to the function are REQUIRED to be exactly the same.

The function takes positional arguments in the form: - at least one variable name to assign to, - followed by a required ':' colon character, - followed by at least one array variable to iterate over.

Without -k option: - For each array variable: - If -s option, sort array keys. - For each element in the array: - Assign the element to the variables in order.

With -k option: - Accumulate all keys of all arrays into a set of keys. - If -s option, sort the set. - For each value in the set of keys: - Assign the values of each array[key] to corresponding variable.

Example

local array1=(a b c d) array2=(d e f g)
while L_foreach a : array1; do echo $a; done  # a b c d
while L_foreach a b : array1; do echo $a,$b; done  # a,b c,d
while L_foreach a b : array1 array2; do echo $a,$b; done  # a,d b,e c,f g,d
while L_foreach a b c : array1 array2; do echo $a,$b; done  # a,d,b e,c,f g,d,<unset>
while L_foreach -R 3 a : array1; do echo ${#a[@]},${a[*]},; done  # 3,a b c, 1,d,

local -A dict1=([a]=b [c]=d) dict2=([a]=e [c]=f)
while L_foreach -R 3 a : dict1; do echo ${#a[@]},${a[*]},; done  # 2,b d or 2,d b
                        # the order of elements is unknown in associative arrays
while L_foreach -s -k k a b : dict1 dict2; do echo $k,$a,$b; done  # a,b,e  c,d,f
while L_foreach -s -k k a b : dict1 dict2; do echo $k,$a,$b; done  # a,b,e  c,d,f

Options:

  • -s Output in sorted keys order. Does nothing on non-associative arrays.
  • -r Output in reverse sorted keys order. Implies -s.
  • -n Output in numeric sorted order. Implies -s.
  • -V Output in sorted values order. Implies -s.
  • -R <num>

    Repeat each variable name as an array variable with indexes from 0 to num-1.

    For example: '-R 3 a : arr' is equal to 'a[0] a[1] a[2] : arr'.

  • -i <var> Index of the loop is sroted in the specified variable. First loop has index 0.
  • -v <var> Store iterator state in the variable, instead of picking unique name starting with L_FOREACH*.
  • -k <var> Key of the first assigned element is stored in the specified variable. Usefull with -s.
  • -f <var> First loop stores 1 into the variable, otherwise 0 is stored in the variable.
  • -l <var> Last loop stores 1 into the variable, otherwise 0 is stored in the variable.
  • -c <var> Count of assigned variables is stored in the variable var.
  • -e <var>

    Existing values are assigned 1 in the corresponding indexes of the specified array variable.

    The indexes of elements with the value 1 are the indexes of assigned variable names. The indexes of empty elements are the indexes of unassigned varaible names.

  • -h Print this help and return 0.

Argument: $@ Variable names to assign, followed by : colon character, followed by arrays variables.

Uses environment variables:

  • _L_FOREACH
  • _L_FOREACH_[0-9]+

Return:

0 if iteration should be continued,

1 on interanl error, 2 on usage error, 4 if iteration should stop.