Skip to content

Instantly share code, notes, and snippets.

@haxwithaxe
Created June 19, 2026 21:32
Show Gist options
  • Select an option

  • Save haxwithaxe/7180ddaf5be29fba405ae65b375b65bb to your computer and use it in GitHub Desktop.

Select an option

Save haxwithaxe/7180ddaf5be29fba405ae65b375b65bb to your computer and use it in GitHub Desktop.
Consume a null separated list on stdin and do something with each element.
#!/bin/bash
set -e
SCRIPT_NAME="${0##./}"
# shellcheck disable=SC2120
print_help() {
local exit_error
exit_error="${1:-1}"
cat - <<EOF
For each null terminated input to stdin do something.
Usage:
$SCRIPT_NAME -h|--help
$SCRIPT_NAME [-d|--debug] [--allow-errors] -s|--source <bash script path>
$SCRIPT_NAME [-d|--debug] [--allow-errors] -r|--run <executable> [-p|--argpos <arg position index>] -- [<executable args>]
Options:
-h|--help: Show this message.
--allow-errors: Adds 'set +e' for sourced script.
-d|--debug: Adds 'set -x' to sourced scripts.
-s|--source <bash script path>: Source the given bash script for each item passed to stdin. The variable \$nulliter_item containing the current line is available to the sourced script.
-r|--run <executable>: Run an executable with the item from stdin at the position given to -p|--argpos.
-p|--argpos <arg position index>: The position of the item in the arguments passed to the executable. Defaults to the last argument.
--sleep: Sleep for some interval after each iteration. The value of this argument is passed to the sleep command.
<executable args>: Arguments to pass to the executable specified with -r|--run.
EOF
if [[ "$exit_error" != "noexit" ]]; then
exit "$exit_error"
fi
}
iternull_cmd() {
local argpos executable args args_after args_before sleep_for
args=()
args_after=()
args_before=()
sleep_for="$1"
shift
argpos="$1"
executable="$2"
shift 2
args=("$@")
if [[ "$argpos" -eq "-1" ]]; then
args_before=("${args[@]}")
else
if [[ "$argpos" =~ -[0-9]+ ]]; then
argpos="$(( ${#args[@]} + argpos ))"
fi
args_before=("${args[@]::$(( argpos - 1 ))}")
args_after=("${args[@]:$(( argpos - 1 ))}")
fi
if [[ "${sleep_for#}" -gt 1 ]] || [[ "$sleep_for" -gt 0 ]]; then
sleep_cmd="sleep $sleep_for"
else
sleep_cmd=":"
fi
while IFS= read -r -d '' nulliter_line; do
"$executable" "${args_before[@]}" "${nulliter_line}" "${args_after[@]}" || exit $?
$sleep_cmd
done
}
iternull_source() {
local script bashopts set_bashopts sleep_for
sleep_for="$1"
shift
script="$(realpath "$1")"
shift
bashopts="$*"
if [[ -n "$bashopts" ]]; then
set_bashopts="set $bashopts"
else
set_bashopts=":"
fi
if [[ "${sleep_for#}" -gt 1 ]] || [[ "$sleep_for" -gt 0 ]]; then
sleep_cmd="sleep $sleep_for"
else
sleep_cmd=":"
fi
export nulliter_item
while IFS= read -r -d '' nulliter_item; do
# shellcheck disable=SC1090
(${set_bashopts}; source "$script")
$sleep_cmd
done
}
main() {
local OPTS bashopts bashopts source_script run_script run_args argpos sleep_for
run_args=()
argpos=-1
sleep_for=-1
OPTS=$(getopt --name "$SCRIPT_NAME" --options hds:r:p: --longoptions help,debug,allow-errors,soruce:,run:,argpos:,sleep: -- "$@") || print_help
eval set -- "$OPTS"
while [[ "$#" -gt 0 ]]; do
case "$1" in
-h|--help)
print_help
;;
--allow-errors)
if [[ -n "$run_script" ]]; then
echo 'WARNING: ignoring '"$1"'. --allow-errors is only used with -s|--source.'>&2
else
bashopts="${bashopts} +e"
fi
shift
;;
-d|--debug)
if [[ -n "$run_script" ]]; then
echo 'WARNING: ignoring '"$1"'. -d|--debug is only used with -s|--source.'>&2
else
bashopts="${bashopts} -x"
fi
shift
;;
-s|--source)
if [[ -n "$run_script" ]]; then
echo 'ERROR: -r|--run and -s|--source are mutually exclusive.'>&2
print_help
fi
source_script="$2"
shift 2
;;
-r|--run)
if [[ -n "$source_script" ]]; then
echo 'ERROR: -r|--run and -s|--source are mutually exclusive.'>&2
print_help
fi
run_script="$2"
shift 2
;;
-p|--argpos)
if [[ -n "$source_script" ]]; then
echo 'WARNING: ignoring '"$1 $2"'. -p|--argpos is only used with -r|--run.'>&2
else
argpos="$2"
fi
shift 2
;;
--sleep)
sleep_for="$2"
shift 2
;;
--)
shift
run_args=("$@")
break
;;
*)
print_help
;;
esac
done
if [[ -n "$run_script" ]]; then
iternull_cmd "$sleep_for" "$argpos" "$run_script" "${run_args[@]}"
elif [[ -n "$source_script" ]]; then
iternull_source "$sleep_for" "$source_script" "$bashopts"
fi
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment