Skip to content

Instantly share code, notes, and snippets.

@kjkuan
Created February 26, 2017 20:40
Show Gist options
  • Save kjkuan/bc9817166b58c255808f86979b6f9a23 to your computer and use it in GitHub Desktop.
Save kjkuan/bc9817166b58c255808f86979b6f9a23 to your computer and use it in GitHub Desktop.
For-All Each-Do Success Fail
#!/usr/bin/env bash
#
# This is a hack that allows you to express loop and conditional processing
# of an array of items in terms of function definitions.
#
# My initial motivation came from the need to process an array of items, and
# then for those successfully processed items, do another different processing
# step, and similarly for the failed items; perform further processing/filtering
# steps for each failed/successful items.
#
# The end result does what I wanted, but also made me realize its limitations.
# Although it's nice to be able to express the processing steps in a tree-like
# fasion via nested function definitions, there's certain performance overhead
# incurred by the use of code generation and function calls.
#
# Moreover, it requires all inputs be stored in an array first in memory, and
# more intermediate arrays are created to hold the successful and failed items
# as they are processed. And lastly, due to dynamic function rewrites at runtime,
# source line numbers in error reports become completely useless.
#
# However, it's an interesting exercise nevertheless, and I believe more
# interesting hacks can come out of the similar ideas or techniques used here.
# Therefore, by sharing the source code, I hope you learn or find something
# useful from it. Comments and suggestions are welcomed!
#
#
# Example:
main () {
local inputs results=()
inputs=($(seq 10)) || return $?
# A Each-Do function definition is required. Its body will be wrapped inside
# a loop that iterates through the successful/failed array from the
# previous step.
Each-Do () {
# $IT expands to the current item in the loop
echo "Processing number $IT"
(( $IT % 2 )) # true if $IT is odd
}
Success () { # for odd numbers
# $_successful is the name of the array holding successfully
# processed items from the previous Each-Do() {...} function.
# There's also a $_failed.
#
local -n odd_nums=$_successful
local odd_len=${#odd_nums[*]}
# You can nest *-Do() definitions inside Success()/Fail().
Each-Do () {
echo "$IT is odd"
(( IT < odd_len ))
}
Success-Do () { # alternative for writing just Success() { Each-Do () { ...; }; }
echo "$IT is < $odd_len"
results+=($(( IT ** 2 )))
}
Fail-Do () { # alternative for writing just Fail() { Each-Do () { ...; }; }
echo "$IT is >= $odd_len"
results+=($IT)
}
# NOTE: A 'For-All $_successful' at the end here is implicit added.
}
Fail () { # for even numbers
local -n even_nums=$_failed
echo "The even numbers are: ${even_nums[*]}"
}
For-All inputs # kicks off the processing
echo === results=============
local each
for each in ${results[*]}; do
echo $each
done
}
# --------------------------------------------------
FOR_ALL_NEXT_ID=0
func2var () { # <func_name> <var_name>
local -n f=${2:?required}
f=$(declare -f ${1:?required}) || return $?
f=${f/$1 /${2}_$((FOR_ALL_NEXT_ID++))_$RANDOM }
}
For-All () { # <array_name>
# $1 should be the name of the array to be processed.
# $1 should start with a character matching [a-z] to avoid clashing
# with For-All's local variables.
local Each_Do; Each_Do=$(declare -f Each-Do) || {
echo "$FUNCNAME: Each-Do () { ...; } is required!" >&2
return 1
}
local Success Success_Do Fail Fail_Do
func2var Success Success
func2var Success-Do Success_Do
func2var Fail Fail
func2var Fail-Do Fail_Do
if [[ $Success && $Success_Do ]]; then
echo "$FUNCNAME: Success and Success-Do can't be used at the same level!" >&2
return 1
fi
if [[ $Fail && $Fail_Do ]]; then
echo "$FUNCNAME: Fail and Fail-Do can't be used at the same level!" >&2
return 1
fi
local _successful=successful_$((FOR_ALL_NEXT_ID++))_$RANDOM
local _failed=failed_$((FOR_ALL_NEXT_ID++))_$RANDOM
local _code=("
Each-Do () {
unset -f Each-Do
local IT $_successful=() $_failed=()
for IT in \"\${${1:?required}[@]}\"; do
if ${Each_Do#*\)}; then
$_successful+=(\"\$IT\")
else
$_failed+=(\"\$IT\")
fi
done
")
local _name
if [[ $Success_Do ]]; then
_name=${Success_Do%% *}
_code+=("
$_name () {
for IT in \"\${$_successful[@]}\"; do
${Success_Do#*\)}
done
}
$_name; unset -f $_name
")
unset -f Success-Do
elif [[ $Success ]]; then
eval "
${Success%\}}
if declare -F Each-Do >/dev/null; then
For-All $_successful
fi
}"
unset -f Success
_name=${Success%% *}
_code+=($_name "unset -f $_name")
fi
if [[ $Fail_Do ]]; then
_name=${Fail_Do%% *}
_code+=("
$_name () {
for IT in \"\${$_failed[@]}\"; do
${Fail_Do#*\)}
done
}
$_name; unset -f $_name
")
unset -f Fail-Do
elif [[ $Fail ]]; then
eval "
${Fail%\}}
if declare -F Each-Do >/dev/null; then
For-All $_failed
fi
}"
unset -f Fail
_name=${Fail%% *}
_code+=($_name "unset -f $_name")
fi
_code+=(\})
local _IFS=$IFS; IFS=$'\n'
eval "${_code[*]}"; IFS=$_IFS
# declare -f Each-Do
Each-Do
}
if [[ $BASH_SOURCE = "$0" ]]; then
main "$@"
fi
@kjkuan
Copy link
Author

kjkuan commented Feb 26, 2017

Disclaimer: Use at your own risk. May contain bugs or hurt your brain. It's Bash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment