Sim, é realmente algo que você pode eu acho. Veja minhas amostras de código abaixo. Eu realmente me perguntei sobre a situação que você descreveu, mas há muito tempo atrás - um problema clássico que vai parecer. Agora, desde então, eu não toquei em bash
por um número de anos, e eu realmente preciso voltar para ${shape}
e ganhar $$ pontos, eu levei isso como um pequeno exercício.
Você precisará de dois arquivos, ambos incluídos abaixo: session_driver.sh
(um análogo muito simplificado de expect
) e sua própria lógica do controlador no formato de script controller_script.sh
para conduzir sua sessão. O driver funciona com o telnet (usando o cliente netcat) e o ssh. Aqui estão dois exemplos de conexão, primeiro no modo ssh (SSH=1)
:
NAME=root SSH=1 HOST=my-nas ./session_driver.sh controller_script.sh
Isso registra você no dispositivo de armazenamento conectado à rede my-nas
como usuário root
para diversão aleatória com o ssh. Você teria que fornecer a senha manualmente, no entanto. E aqui está a invocação do telnet, usando o modo de telnet padrão:
NAME=root PWORD='123' HOST=my-nas ./session_driver.sh controller_script.sh
Aqui você especifica sua senha '123' com PWORD
e, portanto, a sessão de telnet é 100% automatizada.
Os scripts foram testados no Linux.
session_driver.sh
#! /bin/bash
#
# BASH REMOTE SESSION DRIVER
# - for scripted telnet or rsh.
#
# Ref: http://superuser.com/questions/521716/how-to-run-scripts-within-a-telnet-session
#
USAGE=$(cat <<END
Usage: session_driver.sh CONTROLLER_SCRIPT
where CONTROLLER_SCRIPT a bash source file that defines a function named
"controller_script".
Enviroment variables must pass values as the following two examples show.
(a) Connect to telnet server, the default, via nc (netcat):
$ HOST=mybank.com NAME=me PWORD=1234 session_driver.sh milk_account.sh
Password PWORD can be omitted.
(b) Connect to secure shell server via ssh:
$ SSH=1 HOST=mybank.com NAME=me session_driver.sh milk_account.sh
Do not use PWORD in this case. If needed, you'll be prompted by ssh.
Use functions echo_stderr, read_one_line_response, an read_some_lines_response
in your CONTROLLER_SCRIPT. Adjust MULTIPLE_LINES_TIMEOUT when dealing with slow
responses.
END
)
script_name="SESSION_DRIVER"
# ARGUMENTS
if [[ -z "$1" ]] || (echo "$1" | grep -qE "^[-][-]?[h|H]"); then
echo "$USAGE"
exit 1
fi
controller_script_path=$1
# OVERRIDABLE PARAMETERS
HOST=${HOST:?Specify where to connect to!}
NAME=${NAME:?Specify user name}
PWORD=${PWORD:-} # telnet mode only
MULTIPLE_LINES_TIMEOUT=1 # how long to wait for each line of output
# (in seconds)
SSH=${SSH:-0} # use telnet by default
# PLUMBING
tmpdir=$(mktemp -d) # secure (?) place for plumbing stuff
trap 'rm -rf "$tmpdir"; echo "$script_name: all done"' EXIT # get rid of place
# upon termination
# We'll need a backdoor fd so that we can spew output from inside the server
# controller to the outside.
exec 3>&2 # direct fd3 to a copy of fd2 (stderr)
# We'll need an extra pipe for channeling the output of the server back into the
# server controller. A named pipe will do. That's pretty portable.
mkfifo ${tmpdir}/output
# SETUP CONNECTION COMMAND AND CONTROLLER SCRIPT
# Determine mode.
if [ "$SSH" == 1 ]; then
cmd="ssh -t -t $HOST -l $NAME" # -t (twice) forces tty emulation
else
cmd="nc -t $HOST 23"
fi
# Read in the controller script.
if [[ ! -f "$controller_script_path" ]]; then
echo "$script_name: script \"$controller_script_path\"" not found
exit 1
fi
source "$controller_script_path"
if ! (declare -f controller_script > /dev/null); then
echo "$script_name: script didn't define function 'controller_script'"
exit 1
fi
# SERVER CONTROLLER AUXILIARY FUNCTIONS
# Send debugging information via backdoor pipe (fd3).
function echo_stderr () {
echo "$1" >&3
}
# Read exactly one expected line of output and forward it to stdout.
function read_one_line_response () {
local line
# Eat the command line as echoed by telnet.
read < ${tmpdir}/output
# Now get the response line.
read line < ${tmpdir}/output
echo $line | tr -d '\r' # get rid of pesky carriage returns
}
# Read any number of lines of output, as long as they occur sufficiently
# close (within MULTIPLE_LINES_TIMEOUT seconds); pipe all to stdout.
function read_some_lines_response () {
local line
read < ${tmpdir}/output
while read -t ${MULTIPLE_LINES_TIMEOUT} line < ${tmpdir}/output; do
echo $line | tr -d '\r'
done
}
function password_interaction() {
if [[ -n "$NAME" && "$SSH" == 0 ]]; then
# Read characters up to ':'. We won't get a complete line. Hopefully
# we'll see "login".
while true; do
read -d ":" line < ${tmpdir}/output;
(echo $line | grep "login" > /dev/null) && break;
done
echo "$NAME"
read < ${tmpdir}/output
echo "$PWORD"
read < ${tmpdir}/output
else
read_some_lines_response > /dev/null
fi
}
# Push out commands to server on standard output. Read the server's output from
# the pipe. And push logging information on fd3.
function controller() {
password_interaction
controller_script # call the script provided as command line argument
}
# SOLDERING IT ALL TOGETHER
# The controller's standard output goes to the server as its commands. The
# server's standard output and error go to standard output. But thanks to the
# 'tee', the controller is also able to read the server's standard output and
# error through the named pipe. Note backdoor output, produced by the controller
# on fd3, goes to stderr of the pipe line by 'exec' file redirection above.
controller | ${cmd} 2>&1 | tee ${tmpdir}/output
controller_script.sh
# Sample controller script for session_driver, a bash-way of doing 'expect'
# for driving remote telnet or ssh sessions. This script is to be sourced by
# the session_driver.sh script.
# Note our use of stuff defined in the sourcing script:
# - echo_stderr to send logging information via backdoor;
# - read_one_line_response, blocking function, to obtain an expected output
# line;
# - read_some_lines_response, temporarily blocking function, to obtain several
# lines of expected output; and
# - ${tmpdir} for temporary use.
MAX_ITERATIONS=10 # just so that we can bound the example
# MAKE LIST OF INITIAL COMMANDS FOR SERVER
cat > ${tmpdir}/myinitcommands <<- EOF
echo hello
DoesntWork
pwd
EOF
function controller_script () {
local response cmdline n
# PLAY A LIST OF COMMANDS
while read cmdline; do
echo_stderr "CONTROLLER_SCRIPT: execute: $cmdline"
echo "$cmdline"
read_some_lines_response > /dev/null
done < ${tmpdir}/myinitcommands
echo_stderr "CONTROLLER_SCRIPT: done initial commands"
# DO SILLY INTERACTION A NUMBER OF TIMES
# We here carefully read the one-line response to each issued command and
# react accordingly: if the #seconds of the wall clock is even, then we
# ask the server to give us its current directory listing.
for ((n=0; n < $MAX_ITERATIONS; n++)); do
# Make server invoke date function to get number of seconds in current
# minute.
echo_stderr "CONTROLLER_SCRIPT: execute: \"date +f %S\"" # log command
echo "sleep 2; date +%S" # do it!
# Read one line of output. In general, one needs to be careful
# about how much output to read: the read is blocking!
response=$(read_one_line_response)
if ((${response#?} % 2 == 0)); then
echo_stderr "CONTROLLER_SCRIPT: even: $response seconds" # log event
echo "ls /" # execute on server
read_some_lines_response > /dev/null # don't need it here
else
echo_stderr "CONTROLLER_SCRIPT: odd: $response seconds" # log event
sleep 1
fi
done
echo exit # terminate remote session
}