Skip to content

Instantly share code, notes, and snippets.

@njvack
Last active April 4, 2025 14:33
Show Gist options
  • Save njvack/4c5f7c81cd0ee8dffa6788aae93ee22e to your computer and use it in GitHub Desktop.
Save njvack/4c5f7c81cd0ee8dffa6788aae93ee22e to your computer and use it in GitHub Desktop.
Use dtach + script + cat to make a "pick up where I left off" session management tool
#!/bin/bash
USAGE="sessionize: Build a disconnection-tolerant interactive session
Usage: sessionize [session_name] [command ...]
With no arguments, lists active sessions
If session_name is specified, either create or attach to it and run $SHELL as
an interactive login shell.
If session_name and command are specified, create the session and run command.
Uses dtach for session management, and script + cat to try and bring you back
to where you left off.
This is experimental and might suck.
If you don't like the \"rely on a race condition to keep your replayed session
from getting cleared by dtach\" thing, I have a patched dtach that adds a -C
option to stop it from doing that, it's here:
https://github.com/njvack/dtach
Note that by setting SESSIONIZE_STATE_DIR and changing permissions on your
session socket (and its parent directories), you can have multiple people
connect to one session -- so if you want to share a terminal, that should
work. Note that you'll want to have the two terminals at the same size or
things will look weird.
I'm not sure if you can just make the new session non-writeable if you only
want them to see what you're doing.
"
if [ -n "${SESSIONIZE_NAME+x}" ]; then
echo "You're already in a session called ${SESSIONIZE_NAME}"
exit 1
fi
ARGS=("$@")
shopt -sq nullglob
SESSIONIZE_STATE_DIR="${SESSIONIZE_STATE_DIR:-/tmp/$USER/dtach}"
# No arguments, list the active sessions
if [[ $# -eq 0 ]]; then
sockets=("$SESSIONIZE_STATE_DIR"/*sock)
if [[ ${#sockets[@]} == 0 ]]; then
echo "No active sessions."
echo
echo "sessionize -h for help"
exit 0
fi
echo "Active sessions:"
for sock in "${sockets[@]}"; do
base="$(basename "$sock")"
sesname="${base%.*}"
echo "$sesname"
done
echo
echo "sessionize -h for help"
exit 0
fi
if [[ $# -eq 1 && ("$1" == "-h" || "$1" == "--help") ]]; then
printf "%s\n" "${USAGE}" | fmt -w "$(tput cols)"
exit
fi
export SESSIONIZE_NAME="$1"
mkdir -p "${SESSIONIZE_STATE_DIR}"
chmod 700 "${SESSIONIZE_STATE_DIR}"
export SESSIONIZE_LOG="${SESSIONIZE_STATE_DIR}/${SESSIONIZE_NAME}.log"
export SESSIONIZE_DTACH_SOCKET="${SESSIONIZE_STATE_DIR}/${SESSIONIZE_NAME}.sock"
if [ ${#ARGS[@]} -lt 2 ]; then
CMD=("$SHELL -i -l")
else
CMD=("${ARGS[@]:1}")
fi
if [ -S "${SESSIONIZE_DTACH_SOCKET}" ]; then
# Spawn a background subshell that waits 0.1 second and then replays the
# saved session.
# This kind of gross but gets around dtach clearing the screen after attaching
# (sleep 0.1 && cat "${SESSIONIZE_LOG}") &
# dtach -a "$SESSIONIZE_DTACH_SOCKET" -z -r winch
#
# Alternative approach: Use dd to eat the 6-byte "clear" VT100 escape code
# It's not racey but it does leave you in `cat`
# Maybe this is better? It's definitely more fun.
cat "${SESSIONIZE_LOG}"
dtach -a "$SESSIONIZE_DTACH_SOCKET" -z -r winch | (
dd bs=6 count=1 status=none of=/dev/null
cat
)
else
dtach -c "$SESSIONIZE_DTACH_SOCKET" -z -r winch script -q -f -e -c "${CMD[@]}" "$SESSIONIZE_LOG"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment