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
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
File 'report.txt' is 1024 bytes.
File 'image.jpg' is 4096 bytes.
File 'archive.zip' is 16384 bytes.
-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
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
--- 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
--- 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
--- 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
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
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>: Stores1in<var>during the first iteration,0otherwise.-l <var>: Stores1in<var>during the last iteration,0otherwise.
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
[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
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
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
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:
-
$1variable to set -
$2fmt 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:
-
-sOutput in sorted keys order. Does nothing on non-associative arrays. -
-rOutput in reverse sorted keys order. Implies -s. -
-nOutput in numeric sorted order. Implies -s. -
-VOutput 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.
-
-hPrint 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.