Skip to content

Instantly share code, notes, and snippets.

@kousu
Created April 3, 2026 11:47
Show Gist options
  • Select an option

  • Save kousu/5da679673a5e27041e0655b153f66a75 to your computer and use it in GitHub Desktop.

Select an option

Save kousu/5da679673a5e27041e0655b153f66a75 to your computer and use it in GitHub Desktop.
Insane mofo'ing coprocesses
#!/bin/bash
declare -A stdins coprocs
dup() {
eval "exec $1>&$2"
# eval "exec $2<&$1" # or ??? are these equivalent??
# they should both be calling dup2(old_fd, new_fd) in C underneath but .. maybe not?
}
launch() {
title=$1
coproc { while read event; do echo "$title: $event"; done } & coprocs[$title]=$!
stdins[$title]=$((${#stdins[@]} + 4))
exec 3<&${COPROC[0]}
eval "exec ${stdins[$title]}>&${COPROC[1]}" # ugly that we need exec here
cat <&3 & # re-mux (without this you won't get any output unless you cat)
# also you can't 1<&${COPROC[0]} directly, even though that would seem to be the most logical.. ?
# weird:
#exec 3<&${COPROC[0]} # works
# exec ${stdins[$title]}>&${COPROC[1]} # fails; it tries to interpret the fd (.e.g "4") as a command to run
#eval "exec ${stdins[$title]}>&${COPROC[1]}" # works
#: ${stdins[$title]}>&${COPROC[1]} # works but has an implicit subshell around it so it's usefless
}
launch A
launch B
launch C
echo "Hello" >&${stdins[A]}
echo "We are not" >&${stdins[B]}
echo "Computer" >&${stdins[C]}
echo "People" >&${stdins[A]}
echo "I am $$"
set -x; ls -l /proc/$$/fd
# bash
bash --norc <&0 >&1 2>&2 4>&4 5>&5 6>&6
#!/bin/bash
#
DEBOUNCE_TIME=$((5 * 60)) # 5 minutes
DEBOUNCE_TIME=7
debounce() {
# debounce a single stream of events
# this means it takes events and only outputs the *first one* that happens
# TIMEOUT="${1:-60}"; shift
TIMEOUT="${DEBOUNCE_TIME}"
local event
local holding # flag; set when we have an event, reset when we emit it; if we have a series of timeouts in a row, we only output
# we use a flag to distinguish "" from unset
# (there's probably a more bash-ey way to do that? test -u ? idk)
while true; do
if IFS= read -t "$TIMEOUT" -r -d $'\0' new_event; then
: # got an event within the timeout so that means the state is still bouncing; reset the timer and try again
printf "debouncing '%s'\n" "$new_event" >&2
holding=1
event="$new_event"
elif [ $? -eq 142 ]; then
# timeout
if [ -n "$holding" ]; then
printf "emitting '%s'\n" "$event" >&2
printf "%s\0" "$event"
holding=""
event=""
fi
else
# some other error
return 1
fi
done
}
# dup() {
# eval "exec $1>&$2"
# # eval "exec $2<&$1" # or ??? are these equivalent??
# # they should both be calling dup2(old_fd, new_fd) in C underneath but .. maybe not?
# }
declare -A stdins coprocs
spawn_debounce() {
event=$1
coproc { debounce; } & coprocs[$event]=$!
stdins[$event]=$((${#stdins[@]} + 200))
exec 33<&${COPROC[0]}
eval "exec ${stdins[$event]}>&${COPROC[1]}" # ugly that we need exec here
cat <&33 & # re-mux (without this you won't get any output unless you cat all the individual COPROC[0]'s one by one..which is basically what this does.')
# also you can't 1<&${COPROC[0]} directly, even though that would seem to be the most logical.. ?
#
# TODO: clean up children explicitly
# weird:
#exec 3<&${COPROC[0]} # works
# exec ${stdins[$title]}>&${COPROC[1]} # fails; it tries to interpret the fd (.e.g "4") as a command to run
#eval "exec ${stdins[$title]}>&${COPROC[1]}" # works
#: ${stdins[$title]}>&${COPROC[1]} # works but has an implicit subshell around it so it's usefless
}
demux_and_debounce() {
# this *demuxes* the inputs into distinct types
# and sends each to a distinct coproc
while read -r -d $'\0' dir; do
echo "raw event: $dir" >&2
if [ -z "${stdins[$dir]}" ]; then
spawn_debounce "$dir"
fi
printf "%s\0" "$dir" >&${stdins[$dir]}
done
}
read_all() {
while read -r -d $'\0' event; do
if OUTPUT=$($1 "$event"); then
echo "read_all: $1 $event: success" >& 2
printf "%s\0" "$OUTPUT"
else
echo "read_all: $1 $event: failure" >& 2
fi
done
}
git_worktree() {
# convert inotify events into top level git worktree paths
# rev-parse ALWAYS outputs a trailing newline, even
# perl is the only tool I could find which can strip EXACTLY ONE trailing newline
(set -o pipefail;
git -C "$1" rev-parse --show-toplevel | perl -pe 'chomp if eof') &&
printf "\0"
}
git_sync() {
git -C "$1" status
return 0
echo "syncing $1" >&2
if git -C "$1" sync; RC=$?; then
RC=$?
echo "success" >&2
else
echo "err" >&2
RC=$?
fi
echo "done syncing $1; RC=$RC" >&2
}
check() {
# verify all the passed dirs are actually git repos
err=0
for dir in "$@"; do
if ! git -C "$dir" sync --able; then
err=1
fi
done
if [ "$err" -ne 0 ]; then
exit "$err"
fi
}
check "$@" || exit $?
# TIP: you can NUL-delimit things
# inotifywait -m -e close_write,move,delete --format "%w%f%0" "$@" | while read -r -d $'\0' path; do ... done
# but you can't so easily
inotifywait -r -m -e close_write,move,delete --format "%w%0" "$@" | \
read_all git_worktree | \
demux_and_debounce | \
read_all git_sync
# while read -r -d $'\0' event; do
# echo "got $event" >&2
# done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment