Created
June 19, 2026 21:32
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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