You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
7.2 KiB
Bash
193 lines
7.2 KiB
Bash
#!/usr/bin/env -S bash -e
|
|
#
|
|
# Copyright 2021 Nathan L. Conrad
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it under
|
|
# the terms of version 3 of the GNU General Public License as published by the
|
|
# Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
|
|
# Establish usage
|
|
IFS= read -rd '' usage << '__eof__' || true
|
|
Usage: sockhop [-chil] [--] sock [[cmd ...] | [arg ...]]
|
|
|
|
Transport and execute shell commands via Unix domain sockets
|
|
|
|
Optional arguments:
|
|
-c, --clean Imply -l/--listen, ignore SOCKHOP_SEPARATOR, and clear the
|
|
terminal before execution of each command received from the
|
|
socket. Incompatible with -i/--stdin.
|
|
-h, --help Show this help message and exit
|
|
-i, --stdin Read standard input lines and send them to the socket.
|
|
Implied if -c/--clean and -l/--listen are not set and the
|
|
standard input file descriptor is not opened on a terminal.
|
|
Incompatible with -c/--clean and -l/--listen.
|
|
Commands supplied as positional arguments after the socket file
|
|
are sent to the socket before attempting to read lines from
|
|
standard input.
|
|
-l, --listen Create the socket file, accept connections, and execute
|
|
commands received from the socket in the current context.
|
|
Implied if -c/clean is set or if -i/--stdin is not set, the
|
|
socket file is the last argument, and the standard input file
|
|
descriptor is opened on a terminal. Incompatible with
|
|
-i/--stdin. To preserve execution order, only a single
|
|
connection is allowed at any given time. Positional arguments
|
|
after the socket file assign the values of positional
|
|
parameters for executed commands, starting with $1.
|
|
-- Explicitly denote the end of optional arguments
|
|
|
|
Positional arguments:
|
|
sock The Unix domain socket file
|
|
cmd If -c/--clean and -l/--listen are not set, a command which is
|
|
written to the socket. These commands are sent before
|
|
attempting to read lines from standard input if -i/--stdin is
|
|
set or implied.
|
|
arg If -c/--clean or -l/--listen is set, the value of a positional
|
|
parameter for executed commands, starting with $1
|
|
|
|
Environment variables:
|
|
SOCKHOP_SEPARATOR If -c/--clean is not set, but -l/--listen is set or
|
|
implied, a command which is executed between commands
|
|
received from the socket to separate output of the
|
|
received commands. Defaults to ':' if unset or null.
|
|
SOCKHOP_SHELL If -c/--clean is set or -l/--listen is set or implied,
|
|
the shell used to execute commands. Defaults to $SHELL
|
|
if unset or null, '/bin/sh' if SHELL is unset or null.
|
|
SOCKHOP_SHELL_ENV If -c/--clean is set or -l/--listen is set or implied,
|
|
variable assignments which are expanded when
|
|
SOCKHOP_SHELL is invoked to assign the values of
|
|
variables for executed commands
|
|
SOCKHOP_SHELL_NAME If -c/--clean is set or -l/--listen is set or implied,
|
|
the shell name supplied when SOCKHOP_SHELL is invoked,
|
|
which is used in warning and error messages. If unset,
|
|
defaults to the basename of SOCKHOP_SHELL.
|
|
SOCKHOP_SHELL_OPTS If -c/--clean is set or -l/--listen is set or implied,
|
|
optional arguments which are expanded when SOCKHOP_SHELL
|
|
is invoked to set the shell options for executed
|
|
commands. Defaults to '-e' if unset.
|
|
SOCKHOP_SOCK If -c/--clean is set or -l/--listen is set or implied,
|
|
a variable which is assigned the value of the socket file
|
|
argument in the environment of executed commands
|
|
__eof__
|
|
|
|
# Usage: argerr msg
|
|
#
|
|
# Write an argument usage error message to standard error and fail
|
|
#
|
|
# Positional arguments:
|
|
# msg The core error message
|
|
function argerr
|
|
{
|
|
echo "$1. Use -h for help." >&2
|
|
exit 1
|
|
}
|
|
|
|
# Parse optional arguments
|
|
clean=0
|
|
stdin=0
|
|
listen=0
|
|
while (( $# ))
|
|
do
|
|
# If the argument is a single hyphen followed by multiple single-character
|
|
# options, separate the first option from the argument and leave the rest
|
|
# for the next iteration
|
|
(( ${#1} > 2 )) && [[ $1 =~ ^-[^-]*$ ]] && \
|
|
set -- "${1::2}" "-${1:2}" "${@:2}"
|
|
|
|
# Handle the argument
|
|
case $1 in
|
|
-c|--clean)
|
|
(( stdin )) && argerr "$1 is incompatible with -i/--stdin"
|
|
clean=1
|
|
listen=1
|
|
;;
|
|
-h|--help)
|
|
echo -n "$usage"
|
|
exit 0
|
|
;;
|
|
-i|--stdin)
|
|
(( clean )) && argerr "$1 is incompatible with -c/--clean"
|
|
(( listen )) && argerr "$1 is incompatible with -l/--listen"
|
|
stdin=1
|
|
;;
|
|
-l|--listen)
|
|
(( stdin )) && argerr "$1 is incompatible with -i/--stdin"
|
|
listen=1
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
-*|--*)
|
|
argerr "Invalid argument '$1'"
|
|
;;
|
|
*)
|
|
break
|
|
esac
|
|
|
|
# Advance to the next argument
|
|
shift
|
|
done
|
|
|
|
# Pop the socket file argument
|
|
(( $# )) || argerr 'Missing socket file argument'
|
|
sock=$1
|
|
shift
|
|
|
|
# Imply -l/--listen if the standard input file descriptor is opened on a
|
|
# terminal, -i/--stdin is not set, and the socket file is the last argument.
|
|
# Imply -i/--stdin if the standard input file descriptor is not opened on a
|
|
# terminal and -l/--listen is not set.
|
|
if [ -t 0 ]
|
|
then
|
|
(( stdin || $# )) || listen=1
|
|
else
|
|
(( listen )) || stdin=1
|
|
fi
|
|
|
|
# Either listen, receive, and execute or connect and send
|
|
if (( listen ))
|
|
then
|
|
# There is nothing to separate until a command is executed
|
|
separator_required=0
|
|
|
|
# Default the shell, its environment, and options used to execute commands
|
|
shell_env="SOCKHOP_SOCK='$sock'"
|
|
[[ -n $SOCKHOP_ENV ]] && shell_env="$SOCKHOP_ENV $env"
|
|
shell=${SOCKHOP_SHELL:-${SHELL:-/bin/sh}}
|
|
shell_name=${SOCKHOP_SHELL_NAME-$(basename -- "$shell")}
|
|
shell="$shell_env '$shell' ${SOCKHOP_SHELL_OPTS--e} -c --"
|
|
set -- "$shell_name" "$@"
|
|
|
|
# Default the separator command
|
|
separator=${SOCKHOP_SEPARATOR:-:}
|
|
|
|
# Execute received commands until listening fails
|
|
while true
|
|
do
|
|
while read -r cmd
|
|
do
|
|
[[ $cmd = __fail__ ]] && exit 1
|
|
if (( clean ))
|
|
then
|
|
echo -n $'\e[3J' && clear
|
|
elif (( separator_required ))
|
|
then
|
|
env -S "$shell" "$separator" "$@" || true
|
|
else
|
|
separator_required=1
|
|
fi
|
|
env -S "$shell" "$cmd" "$@" || true
|
|
done < <(socat -u "unix-listen:$sock" stdout || echo __fail__)
|
|
done
|
|
else
|
|
# Send positional parameters then, if applicable, standard input
|
|
(( $# )) && printf -v cmds '%s\n' "$@" || cmds=
|
|
(( stdin )) && arg=- || arg=
|
|
cat <(echo -n "$cmds") $arg | socat -u stdin "unix-connect:$sock"
|
|
fi
|