Last active
April 4, 2025 14:33
-
-
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
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 | |
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