initial commit
This commit is contained in:
commit
498481c491
|
|
@ -0,0 +1,283 @@
|
||||||
|
#!/bin/bash
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
## Setup configuration
|
||||||
|
CONFIG_FILE="$HOME/.sessionrc"
|
||||||
|
typeset -A config
|
||||||
|
# Set defaults
|
||||||
|
config=(
|
||||||
|
[socket_dir]="/tmp/"
|
||||||
|
[socket_base]="session-$UID"
|
||||||
|
[shell]="/bin/bash"
|
||||||
|
[redraw]="winch"
|
||||||
|
)
|
||||||
|
# Source from configuration file if available
|
||||||
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
|
while read line
|
||||||
|
do
|
||||||
|
if echo $line | grep -F = &>/dev/null
|
||||||
|
then
|
||||||
|
varname=$(echo "$line" | cut -d '=' -f 1)
|
||||||
|
config[$varname]=$(echo "$line" | cut -d '=' -f 2- | envsubst)
|
||||||
|
fi
|
||||||
|
done < $CONFIG_FILE
|
||||||
|
fi
|
||||||
|
|
||||||
|
## Set some variables
|
||||||
|
SOCKET_PREFIX="${config[socket_dir]}/${config[socket_base]}-"
|
||||||
|
SOCKET_SUFFIX=".socket"
|
||||||
|
|
||||||
|
# Exit codes
|
||||||
|
EXIT_SUCCESS=0
|
||||||
|
EXIT_INVALID_MODE=1
|
||||||
|
EXIT_SESSION_EXISTS=2
|
||||||
|
EXIT_SESSION_NOT_EXIST=3
|
||||||
|
EXIT_ALREADY_IN_SESSION=4
|
||||||
|
EXIT_INVALID_CONFIG=5
|
||||||
|
EXIT_CONFIG_EXISTS=6
|
||||||
|
EXIT_INVALID_CHOICE=7
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
print_help() {
|
||||||
|
echo >&2 "Usage: $(basename $0) mode [args]"
|
||||||
|
echo >&2 "Manage detachable shell sessions using 'dtach'."
|
||||||
|
echo >&2 "Available modes: "
|
||||||
|
echo >&2 " attach | att | a attach to an existing session"
|
||||||
|
echo >&2 " new | | n create a new session"
|
||||||
|
echo >&2 " terminate | term | t terminate an existing session (SIGTERM)"
|
||||||
|
echo >&2 " kill | | k send SIGKILL to an existing session"
|
||||||
|
echo >&2 " list | ls | l list all available sessions"
|
||||||
|
echo >&2 " info | | i print information on a session"
|
||||||
|
echo >&2 " help | | h display this help message"
|
||||||
|
echo >&2 ""
|
||||||
|
echo >&2 "$(basename $0) attach [name]"
|
||||||
|
echo >&2 " Attach to specified session. If no session name is provided, an interactive"
|
||||||
|
echo >&2 " selection menu is provided. Use <Ctrl-\\> to detach."
|
||||||
|
echo >&2 ""
|
||||||
|
echo >&2 "$(basename $0) new [name]"
|
||||||
|
echo >&2 " Create a new session. If no session name is provided, a generic name will"
|
||||||
|
echo >&2 " be generated. Use <Ctrl-\\> to detach."
|
||||||
|
echo >&2 ""
|
||||||
|
echo >&2 "$(basename $0) terminate [name]"
|
||||||
|
echo >&2 " Terminate a specified session. If no session name is provided, all sessions"
|
||||||
|
echo >&2 " will be terminated upon confirmation."
|
||||||
|
echo >&2 ""
|
||||||
|
echo >&2 "$(basename $0) kill [name]"
|
||||||
|
echo >&2 " Same as 'terminate' but SIGKILL is sent to the shell instead of SIGTERM."
|
||||||
|
echo >&2 ""
|
||||||
|
echo >&2 "$(basename $0) list"
|
||||||
|
echo >&2 " List all available sessions."
|
||||||
|
echo >&2 ""
|
||||||
|
echo >&2 "$(basename $0) info [name]"
|
||||||
|
echo >&2 " Print information on a specified session. If no session name is provided,"
|
||||||
|
echo >&2 " information on all sessions will be printed. The output contains the session"
|
||||||
|
echo >&2 " name, the timestamp of creation and the path of its socket."
|
||||||
|
echo >&2 ""
|
||||||
|
echo >&2 "$(basename $0) help"
|
||||||
|
echo >&2 " Print this help message."
|
||||||
|
echo >&2 ""
|
||||||
|
echo >&2 "$(basename $0) is configured using the file '$CONFIG_FILE', which, if"
|
||||||
|
echo >&2 "non-existent, can be initialized with '$(basename $0) config generate'."
|
||||||
|
}
|
||||||
|
|
||||||
|
get_available_sessions() {
|
||||||
|
local socket_paths=(${SOCKET_PREFIX}*${SOCKET_SUFFIX})
|
||||||
|
local socket_names=()
|
||||||
|
for spath in ${socket_paths[@]}; do
|
||||||
|
sname=${spath##${SOCKET_PREFIX}}
|
||||||
|
sname=${sname%%${SOCKET_SUFFIX}}
|
||||||
|
socket_names+=("${sname}")
|
||||||
|
done
|
||||||
|
echo ${socket_names[@]}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_session_path() {
|
||||||
|
echo "${SOCKET_PREFIX}${1}${SOCKET_SUFFIX}"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_existing_session_path() {
|
||||||
|
if [ -S $1 ]; then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_existing_session_name() {
|
||||||
|
is_existing_session_path $(get_session_path $1)
|
||||||
|
}
|
||||||
|
|
||||||
|
get_new_session_name() {
|
||||||
|
local ii=1
|
||||||
|
while is_existing_session_name $ii; do
|
||||||
|
ii=$((ii+1))
|
||||||
|
done
|
||||||
|
echo $ii
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_if_in_session() {
|
||||||
|
if [ ! -z $DTACH_SOCKET_PATH ]; then
|
||||||
|
echo >&2 "Cannot attach to a session from within a session."
|
||||||
|
exit $EXIT_ALREADY_IN_SESSION
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_if_existing_session() {
|
||||||
|
if ! is_existing_session_name $session_name; then
|
||||||
|
echo >&2 "Session '$session_name' does not exist."
|
||||||
|
exit $EXIT_SESSION_NOT_EXIST
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_if_nonexisting_session() {
|
||||||
|
if ! is_existing_session_name $session_name; then
|
||||||
|
echo >&2 "Session '$session_name' does not exist."
|
||||||
|
exit $EXIT_SESSION_NOT_EXIST
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
print_session_info() {
|
||||||
|
local session_name=$1
|
||||||
|
local session_path=$(get_session_path $session_name)
|
||||||
|
local session_time=$(date -r $session_path "+%Y-%m-%d %H:%M:%S")
|
||||||
|
printf "%-24s %-20s %s\n" "$session_name" "$session_time" "$session_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
## Parse first argument (= mode argument)
|
||||||
|
if [[ "$#" -lt 1 ]]; then
|
||||||
|
print_help
|
||||||
|
exit $EXIT_INVALID_MODE
|
||||||
|
fi
|
||||||
|
mode=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
## Now enter mode specific code
|
||||||
|
case $mode in
|
||||||
|
"attach"|"att"|"a")
|
||||||
|
exit_if_in_session
|
||||||
|
# If no session name specified: select interactively
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
sessions_avail=($(get_available_sessions))
|
||||||
|
if [ ${#sessions_avail[@]} -eq 0 ]; then
|
||||||
|
exit $EXIT_SUCCESS
|
||||||
|
fi
|
||||||
|
echo >&2 "Select a session (q to quit)"
|
||||||
|
select session_name in ${sessions_avail[@]}
|
||||||
|
do
|
||||||
|
if [ -z "$session_name" ]; then
|
||||||
|
exit $EXIT_SUCCESS
|
||||||
|
fi
|
||||||
|
exit_if_nonexisting_session $session_name
|
||||||
|
break
|
||||||
|
done
|
||||||
|
else
|
||||||
|
session_name=$1
|
||||||
|
exit_if_nonexisting_session $session_name
|
||||||
|
fi
|
||||||
|
session_path=$(get_session_path $session_name)
|
||||||
|
CMD="dtach -a $session_path -r ${config[redraw]}"
|
||||||
|
exec $CMD
|
||||||
|
;;
|
||||||
|
"new"|"n")
|
||||||
|
exit_if_in_session
|
||||||
|
#PROMPT_COMMAND="echo -ne \"\033]0;$1 (on $HOSTNAME)\007\""
|
||||||
|
# PS1="\[\e]0;My Titlito\a\]>>"
|
||||||
|
# If no session name specified: generate a generic one
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
session_name=$(get_new_session_name)
|
||||||
|
else
|
||||||
|
session_name=$1
|
||||||
|
exit_if_existing_session $session_name
|
||||||
|
fi
|
||||||
|
session_path=$(get_session_path $session_name)
|
||||||
|
CMD="env DTACH_SOCKET_PATH=$session_path DTACH_SESSION_NAME=$session_name "
|
||||||
|
CMD+="dtach -c $session_path -r ${config[redraw]} ${config[shell]}"
|
||||||
|
echo $CMD
|
||||||
|
exec $CMD
|
||||||
|
;;
|
||||||
|
"terminate"|"term"|"t"|"kill"|"k")
|
||||||
|
# Check if we are in terminate/kill mode
|
||||||
|
if [[ ${mode::1} == "k" ]]; then
|
||||||
|
SIGNAL="-9" # SIGKILL
|
||||||
|
CLEANUP=1 # requires manual cleanup
|
||||||
|
else
|
||||||
|
SIGNAL="-15" # SIGTERM
|
||||||
|
CLEANUP=0
|
||||||
|
fi
|
||||||
|
# If no session name specified: terminate all sessions
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
read -p "Terminate all sessions? [y/N] " -n 1 -r
|
||||||
|
printf "\n"
|
||||||
|
case $REPLY in
|
||||||
|
"y"|"Y")
|
||||||
|
session_name=($(get_available_sessions))
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "no"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
session_name=$1
|
||||||
|
exit_if_nonexisting_session $session_name
|
||||||
|
fi
|
||||||
|
for sname in ${session_name[@]}; do
|
||||||
|
spath=$(get_session_path $sname)
|
||||||
|
PID=$(fuser $spath 2> /dev/null)
|
||||||
|
CMD="kill $SIGNAL $PID"
|
||||||
|
exec $CMD
|
||||||
|
if [[ "$CLEANUP" -eq 1 ]]; then
|
||||||
|
CMD="rm $spath"
|
||||||
|
exec $CMD
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
exit $EXIT_SUCCESS
|
||||||
|
;;
|
||||||
|
"list"|"ls"|"l")
|
||||||
|
sessions_avail=($(get_available_sessions))
|
||||||
|
for sname in ${sessions_avail[@]}; do
|
||||||
|
printf "$sname"
|
||||||
|
if [[ "$sname" == "$DTACH_SESSION_NAME" ]]; then
|
||||||
|
printf "*"
|
||||||
|
fi
|
||||||
|
printf "\n"
|
||||||
|
done
|
||||||
|
exit $EXIT_SUCCESS
|
||||||
|
;;
|
||||||
|
"info"|"i")
|
||||||
|
# If no session name specified: info for all sessions
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
session_name=($(get_available_sessions))
|
||||||
|
else
|
||||||
|
session_name=$1
|
||||||
|
exit_if_nonexisting_session $session_name
|
||||||
|
fi
|
||||||
|
for sname in ${session_name[@]}; do
|
||||||
|
print_session_info $sname
|
||||||
|
done
|
||||||
|
exit $EXIT_SUCCESS
|
||||||
|
;;
|
||||||
|
"help"|"h")
|
||||||
|
print_help
|
||||||
|
exit $EXIT_SUCCESS
|
||||||
|
;;
|
||||||
|
"config")
|
||||||
|
if [[ "$1" != "generate" ]]; then
|
||||||
|
echo >&2 "Use '$(basename $0) config generate' to generate default configuration file."
|
||||||
|
exit $EXIT_INVALID_CONFIG
|
||||||
|
fi
|
||||||
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
|
echo >&2 "Configuration file '$CONFIG_FILE' already exists. Please remove it first."
|
||||||
|
exit $EXIT_CONFIG_EXISTS
|
||||||
|
fi
|
||||||
|
printf "" > $CONFIG_FILE
|
||||||
|
for key in ${!config[@]}; do
|
||||||
|
echo "${key}=${config[$key]}" >> $CONFIG_FILE
|
||||||
|
done
|
||||||
|
exit $EXIT_SUCCESS
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo >&2 "Invalid mode: ${mode}"
|
||||||
|
print_help
|
||||||
|
exit $EXIT_INVALID_MODE
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Loading…
Reference in New Issue