Skip to content

Instantly share code, notes, and snippets.

@jul
Last active May 23, 2024 13:59
Show Gist options
  • Save jul/e9dcfdfb2490f6df3930dfe8ee29ead1 to your computer and use it in GitHub Desktop.
Save jul/e9dcfdfb2490f6df3930dfe8ee29ead1 to your computer and use it in GitHub Desktop.
pubsub server in bash (requires djb tcpserver)
#!/usr/bin/env bash
# https://gist.github.com/jul/e9dcfdfb2490f6df3930dfe8ee29ead1
# WTFPL2.0 do WTF you want with the code except claiming paternity
# require ucspi-tcp http://cr.yp.to/ucspi-tcp/tcpserver.html
# server launched with tcpserver localhost 1234 ./pubsub.sh
# client launched with tcpclient localhost 1234 ./psclient.sh # https://gist.github.com/jul/e9dcfdfb2490f6df3930dfe8ee29ead1#file-psclient-sh
# TODO unsub
# TODO trap SIHGUP to make file rotations (avoiding to have to stop/start server to do so)
set -e
declare -a action;
declare -a ppids;
#set -x
DEBUG=1
echo "BEGINNING!"
RD='\e[31m'
GB='\e[33m'
BL='\e[34m'
GR='\e[0;90m\e[1;47m'
RZ='\e[0m'
#delcr() { echo "$*" | sed -e 's/\n\r$/\n/'; }
atomic_create () {(set -o noclobber;>"$1") &>/dev/null; }
d() {
(( $DEBUG )) && echo -e "# [D]:$(date +"%H:%M:%S"):$GR $* $RZ";
}
push() {
local stack
declare -a stack
stack=( "$@" )
for ((i=${#@}; i--; i)); do
action=( "${stack[$i]}" "${action[@]}" );
done
}
trim () {
echo "$@" | delcr
}
check () { echo "$@" | perl -ane '/^([a-z0-9]+)$/ or die "[\e[31mE\e[0m] illicit characters detected only [a-z0-9] authorized"' 2>&1 ;}
w() {
echo -e "# [${BL}W${RZ}] $@" ;
}
i() {
echo -e "# [${GB}I${RZ}] $@" ;
}
[ -d out ] || ( mkdir out; chmod 2060 out )
e() {
local EX="$1"
shift
echo -e "# [${BL}EXITING${RZ}] ${*}"
exit $EX
}
pub() {
local channel=$1;
shift
check $channel || (w illicit channel name $channel ; return )
[ -f out/$channel ] || ( i "creating <$channel>" )
if [ -f out/$channel.lock ]; then
w lock taken;
return
fi
atomic_create out/$channel.lock
echo -n "$USER on $channel at $(date +'%H:%M:%S'): " >> out/$channel
while (( ${#action} )); do
echo -n $( echo ${action[0]} | delcr ) >> out/$channel
echo -n " " >> out/$channel
action=("${action[@]:1}")
done
echo >> out/$channel
echo
rm out/$channel.lock;
}
exit_script() {
d "killing all subs"
for pid in "${ppids[@]}"; do
kill $pid;
done
e 1 "byebye"
trap - SIGINT SIGTERM SIGPIPE # clear the trap
kill -- -$$ # Sends SIGTERM to child/sub processes
}
trap exit_script SIGINT SIGTERM SIGPIPE
dispatch_action() {
while [[ ${#action} -gt 0 ]]; do
# pop in shell :D
act=${action[0]}
action=("${action[@]:1}")
# end of pop
case $act in
pub)
channel=$( trim ${action[0]} )
action=("${action[@]:1}")
pub $channel
;;
sub)
argument=$( trim ${action[0]} )
d "subscribing <$argument>"
action=("${action[@]:1}")
if [ ! -f out/$argument ]; then
w "<$argument> doesn't exist. Publish on channel to create ot"
break
fi
if check $argument; then
tail -f out/$argument &
ppids+=( "$!" );
fi
;;
bye)
exit_script
;;
\")
d publishing in general
push "pub" "general"
;;
\?)
cat <<EOF
<channel> must be in form [a-z0-9]+
?
this help
pub <channel> [line of text to add in channel]
publish on channel the text
create channel if inexistant silently
sub <channel>
suscribe to channel
bye
exit cleanly and politely
"
publish on general channel to which you are subscribed at startup
EOF
;;
*)
i "unrecognized action <$act>. Try ? for help"
break
;;
esac
done
}
push '"' "new subscriber entering"
dispatch_action
push "sub" "general"
dispatch_action
while [ 1 ]; do
[ -z "$SILENT" ] || echo -n "ready> "
[ -z "$SILENT" ] && read -s -r -a action || read -a action
action=$( echo $action | delcr )
dispatch_action
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment