#!/bin/bash -f
#
# jeanlubatti@yahoo.com
##########################
# License is GPL.
# About this program: JPS stands for John's Parrallel SSH.
# I started working on this during my free time in Webraska (my previous company). It was a pretext
# to learn Bash, RPMS, see a little inside the man pages etc...
# It was a funny project and if it can be of use to anyone... enjoy.
#
# It is written in Bash, and sometimes a very poor written one :(
#
# Basically, I learned what was possible really with Bash when writing this
# so there is a *LOT* of possible improvements and probably as many bugfixes/optimizations.
#
# Some choices are poor (see $maxnode for example) but they will wait for a rewrite.
#
# Changelog:
# Fri Dec 16 2005:
# - Added get function.
# - Corrected documentation
# - Corrected cariage return bug in explore function (silly me)
# - BUGFIX: you can now "set aggregate_output yes" from cmd line
# Wed Dec 14 2005:
# - Massive re-ordering of the code in order to break parse_input into
# smaller functions
# - Fixed bug about lists and macros dir not being saved in config file
# - Colorized syntax error handling
# - Reviewed the lists handling, complete rewrite feasable soon
# - Probably added 20 bugs or more...
# Sun Dec 11 2005:
# - Bugfix with log output after a local "cd".
# Sat Nov 5 2005:
# - Added regex handling on nodes name
#
CVS_VERSION='$Revision: 1.13 $'
CVS_DATE='$Date: 2005/12/16 16:52:02 $'
AUTHOR='$Author: jlubatti $'
RPM_VERSION='1.2-3b'
GLOBAL_CONF_FILE=/etc/jps.conf
echo "JPS: John's Parrallel SSH
? for help"
if [ ! -f ${GLOBAL_CONF_FILE} ]
then
USE_SSH_TIMEOUT=yes
#default vars:
JPSPROMPT="#>"
ECHOLINE="echo -ne ============================================================"
COLOR_A=32
COLOR_B=34
COLOR_C=31
COLOR_NORMAL=39
PRINT_NODES="no"
FILE_HOST_LIST=
OUTPUT="$HOME/.jps/jps.log"
SERIALIZE="off"
SSHUSER=`whoami`
maxnode=255
TEMPO_SLICE=
TEMPO_TIME=
SSH_TIMEOUT=10
PRINT_SUMMARY='yes'
AGGREGATE_OUTPUT='yes'
STRIP_EXPLORE='yes'
else
. ${GLOBAL_CONF_FILE}
fi
function init_start()
{
#ARGS
# none
ssh-add -l >/dev/null 2>&1
if [ "$?" -ne 0 ] ; then
if [ ! -f $HOME/.ssh/id_dsa ] && [ ! -f $HOME/.ssh/id_rsa ] ; then
echo "ERROR ! NO SSH KEY LOADED OR NO AGENT AND NO PRIVATE SSH KEY FOUND IN $HOME/.ssh" ; exit 255 ; fi
echo "WARNING, NO SSH KEY LOADED OR NO AGENT\nYou probably need a loaded ssh key in order to use this wonderfull script..." ; fi
ROOT_JPS="${HOME}/.jps"
JPS_HISTORY="${ROOT_JPS}/jps_history"
CONF_FILE=${ROOT_JPS}/jps.conf
if [ -z "${MACROS_DIR}" ] ; then
MACROS_DIR="${ROOT_JPS}/macros" ; fi
if [ -z "${LISTS_DIR}" ] ; then
LISTS_DIR="${ROOT_JPS}/lists" ; fi
if [ ! -d ${ROOT_JPS} ] ; then
mkdir ${ROOT_JPS} ; fi
if [ ! -f ${CONF_FILE} ] ; then
touch ${CONF_FILE} ;fi
if [ ! -d ${MACROS_DIR} ] ; then
mkdir ${MACROS_DIR} ; fi
if [ ! -d ${LISTS_DIR} ] ; then
mkdir ${LISTS_DIR} ; fi
ROOT_TMP="${ROOT_JPS}/tmp.$$"
OUT_DIR="${ROOT_TMP}/1"
ERR_DIR="${ROOT_TMP}/2"
END_DIR="${ROOT_TMP}/end"
AGGR_DIR="${ROOT_TMP}/AGGREGATE"
TMP_STR="${ROOT_TMP}/TMP_STR"
mkdir "${ROOT_TMP}" "${AGGR_DIR}" "${OUT_DIR}" "${ERR_DIR}" "${END_DIR}"
if [ $? -ne 0 ] ; then
exit -1 ; fi
echo -ne "Loading..."
. ${CONF_FILE}
echo " Done"
}
trap 'rm -rf ${ROOT_TMP}; exit 13' TERM INT
trap 'rm -rf ${ROOT_TMP}' EXIT
function init_colors()
{
#ARGS
# 1 => <DIGIT> : color a
# 2 => <DIGIT> : color b
# 3 => <DIGIT> : color c
# 4 => <DIGIT> : normal color
SETCOLOR_A="echo -en \\033[1;${1}m"
SETCOLOR_B="echo -en \\033[1;${2}m"
SETCOLOR_C="echo -en \\033[1;${3}m"
SETCOLOR_NORMAL="echo -en \\033[0;${4}m"
}
function syntax_error()
{
$SETCOLOR_C
echo "Syntax error: $1 (please see ? for detail)"
$SETCOLOR_NORMAL
}
function syntax()
{
#ARGS
# none
echo "Syntax: jps [-l <hostlist> ] [ -u <user> ] [ -d ]"
}
function add_node()
{
#ARGS
# 1 => <STRING> : Node name
local index=0
while [ -n "${node[index]}" ]
do
index=$(( index + 1 ))
done
node[index]=${1}
}
function clean_node()
{
#ARGS
# none
local index=0
for index in `seq 0 $maxnode`
do
node[index]=''
done
}
function clean_run_node()
{
#ARGS
# none
local index=0
for index in `seq 0 $maxnode`
do
runnodes[index]=''
done
}
function push_key()
{
cat $HOME/.ssh/id_dsa.pub | ssh ${SSHUSER}@${1} "cat > id_dsa.pub ; if [ ! -d .ssh ] ; then mkdir .ssh ; fi ; chmod 700 .ssh ; cat id_dsa.pub >> .ssh/authorized_keys2 ; chmod 600 .ssh/authorized_keys2 ; rm id_dsa.pub "
}
function delete_node()
{
#ARGS
# 1 => <STRING> : node name
local rem=0
for rem in `seq 0 $maxnode`
do
if [ "${node[rem]}" = "$1" ]
then
node[rem]=''
for index in `seq $rem $(( $maxnode -1 ))`
do
next=$(( $index + 1 ))
node[index]=${node[next]}
done
fi
done
}
function display_nodes()
{
# ARGS
# none ( could send number of columns... )
local index=0
local NEXT=0
local MOVE_TO_COL25="echo -en \\033[25G"
local MOVE_TO_COL50="echo -en \\033[50G"
echo "`$SETCOLOR_A`Loaded nodes:`$SETCOLOR_B`"
for index in `seq 0 $maxnode`
do
if [ -n "${node[index]}" ]
then
case "$NEXT" in
"0")
echo -ne "=> ${node[index]}"
NEXT=1
;;
"1")
$MOVE_TO_COL25
echo -ne "=> ${node[index]}"
NEXT=2
;;
"2")
$MOVE_TO_COL50
echo "=> ${node[index]}"
NEXT=0
;;
esac
fi
done
$SETCOLOR_NORMAL
echo
}
function display_help()
{
# ARGS:
# none
$SETCOLOR_A
echo "Commands:
help | ? This Help screen
======================================================
load <list> Flush node list and load <list>
add node <host> Add <host> to the node list
list <list> Add <list> to the node list
del node <host> Remove <host> from the node list
macro <macro> Remove (delete) <macro>
list <list> Remove (delete) <list>
explore <range> Scan an IP range and add a node for each IP that have port 22 open.
<range> need to be of a form understood by nmap (192.168.0.0/16 or 192.168.0-3.*)
pushkey <node list> Installs your public ssh key (found in \$HOME/.ssh/id_dsa.pub) on <node(s) list>
(comma separated list, NO BLANK !) You obviously need to know the password...
set tempo <node number> <seconds> Set a tempo policy. When in parrallel mode, jps will wait <seconds> every <node number> nodes
color_a <color> Set color for help and banners (default 32: green)
color_b <color> Set color for standard output (default 34: blue)
color_c <color> Set color for error output (default 31: red)
color_normal <color> Set color for normal display (default 39: grey)
serialize <on | off> Set if command are serialized or launched in parrallel (default off)
print_nodes <yes | no> Set this to yes in order to display node list before each command launch.
print_summary <yes | no> Set this to yes for having a return codes summary.
aggregate_output <yes | no> If yes, JPS will try to regroup similar outputs.
Default is yes but if number of node is huge and commands are not likely to have the same output,
set it to no.
user <user> Set ssh user to use (default webraska)
log <file> Set logfile to <file>
listdir <dir> Look for lists in <dir>
macrodir <dir> Look for macros in <dir>
ssh_timeout Set ssh timeout (ConnectTimeout option)
strip_explore <yes | no> If yes, only use simple hostname. If no, use FQDN. (default yes)
clear log Remove logging
tempo Remove tempo policy
nodes Clear node list from memory
edit macro <macro> Edit <macro>
list <list> Edit <list> (you may need to reload it after edit)
write list <file> Write nodelist to <file>
mem Write conf variables in preference file
! <command> Execute <command> locally
cd <path> Change directory to <path> locally
CD <path> Change directory to <path> on all nodes
======================================================
show user Show ssh user
serialize Show serialize status
nodes Show all nodes
output Show output status
tempo Show tempo policy
color Show color settings
conf Show everything
======================================================
exec <node list> <macro> Execute <macro> on <nodelist> (comma separated list, NO BLANK !)
all <macro> Execute <macro> on all nodes
run <node list> <command> Execute <command> on <node(s) list> (comma separated list, NO BLANK !)
all <command> Execute <command> on all nodes
put <node list> <file> Put <file> on <node(s) list> (comma separated list, NO BLANK !)
all <file> Put <file> on all nodes
get <node list> <file> Get <file> on <node(s) list> (comma separated list, NO BLANK !)
all <file> Get <file> on all nodes
======================================================
exit Exit from JPS
version $RPM_VERSION ($CVS_VERSION) by $AUTHOR
"
$SETCOLOR_NORMAL
}
function explore_network()
{
# ARGS:
# 1 => <STRING> : Network class
# look for nmap...
which nmap >/dev/null 2>&1
if [ $? -ne 0 ]
then
echo "I need nmap for this... :( sorry..."
else
# check the arg here, then, build the list.
clean_node
for f in `nmap -P0 -sT -p 22 ${1} 2>/dev/null | awk '$1~/Interesting/{HOST=$4} $2~/open/{print HOST}' | tr -d ':' `
do
if [ "${STRIP_EXPLORE}" = "yes" ]
then
# ok.. we have either the IP address OR the hostname.
# in case this is an IP
ISIP=`echo "$f" | awk '$1~/[0-9]?[0-9]?[0-9]\.[0-9]?[0-9]?[0-9]\.[0-9]?[0-9]?[0-9]\.[0-9]?[0-9]?[0-9]$/ { print $0}'`
if [ -z "${ISIP}" ]
then
F=`echo $f | cut -d '.' -f 1`
else
F=${f}
fi
else
F=${f}
fi
add_node $F
done
fi
}
function set_variable
{
#ARGS
# 1 => <ANY> : Variable name
# 2 => <ANY> : Value
# 3 => <ANY> : Optionnal parameter for value (see tempo)
case "${1}" in
"aggregate_output")
case "${2}" in
"yes" )
AGGREGATE_OUTPUT="yes"
;;
"no" )
AGGREGATE_OUTPUT="no"
;;
* )
syntax_error "<yes | no>"
;;
esac
;;
"strip_explore")
case "${2}" in
"yes" )
STRIP_EXPLORE="yes"
;;
"no" )
STRIP_EXPLORE="no"
;;
* )
syntax_error "<yes | no>"
esac
;;
"ssh_timeout")
if [ -n "${2}" ]
then
syntax_error "<value>"
else
SSH_TIMEOUT="${2}"
fi
;;
"listdir")
if [ ! -d "${2}" ]
then
syntax_error "${2} is not a directory"
else
LISTS_DIR="${2}"
fi
;;
"macrodir")
if [ ! -d "${2}" ]
then
syntax_error "${2} is not a directory"
else
MACROS_DIR="${2}"
fi
;;
"user" )
SSHUSER=${2}
echo "USER set to $SSHUSER"
;;
"tempo")
if [ ! -n "${3}" ]
then
syntax_error "TIME SLICE"
else
TEMPO_SLICE="${2}"
TEMPO_TIME="${3}"
echo "tempo set: Wait $TEMPO_TIME second(s) every $TEMPO_SLICE node(s)"
fi
;;
"serialize")
case "${2}" in
"on" )
SERIALIZE="on"
;;
"off" )
SERIALIZE="off"
;;
* )
syntax_error "< on | off >"
;;
esac
;;
"color_a" )
COLOR_A="${2}"
init_colors $COLOR_A $COLOR_B $COLOR_C $COLOR_NORMAL
;;
"color_b" )
COLOR_B="${2}"
init_colors $COLOR_A $COLOR_B $COLOR_C $COLOR_NORMAL
;;
"color_c" )
COLOR_C="${2}"
init_colors $COLOR_A $COLOR_B $COLOR_C $COLOR_NORMAL
;;
"color_normal" )
COLOR_NORMAL="${2}"
init_colors $COLOR_A $COLOR_B $COLOR_C $COLOR_NORMAL
;;
"print_nodes" )
case "${2}" in
"yes" )
PRINT_NODES="yes"
;;
"no" )
PRINT_NODES="no"
;;
* )
syntax_error "< yes | no >"
;;
esac
;;
"log" )
OUTPUT="${2}"
echo "Output log set to $OUTPUT"
;;
"print_summary" )
case "${2}" in
"yes" )
PRINT_SUMMARY="yes"
;;
"no" )
PRINT_SUMMARY="no"
;;
* )
syntax_error "< yes | no >"
;;
esac
;;
* )
syntax_error "Can't set this.."
;;
esac
}
function write_conf()
{
#ARG
# none.
# TODO: Add possibility to write an to an alternate conf file use $CONF_FILE for default
echo "
#Conf file written on `date`:
ECHOLINE=\"${ECHOLINE}\"
COLOR_A=\"${COLOR_A}\"
COLOR_B=\"${COLOR_B}\"
COLOR_C=\"${COLOR_C}\"
COLOR_NORMAL=\"${COLOR_NORMAL}\"
PRINT_NODES=\"$PRINT_NODES\"
PRINT_SUMMARY=\"${PRINT_SUMMARY}\"
OUTPUT=\"$OUTPUT\"
SERIALIZE=\"$SERIALIZE\"
SSHUSER=\"$SSHUSER\"
maxnode=\"$maxnode\"
TEMPO_TIME=\"$TEMPO_TIME\"
TEMPO_SLICE=\"$TEMPO_SLICE\"
SSH_TIMEOUT=\"${SSH_TIMEOUT}\"
AGGREGATE_OUTPUT=\"${AGGREGATE_OUTPUT}\"
STRIP_EXPLORE=\"${STRIP_EXPLORE}\"
LISTS_DIR=\"$LISTS_DIR\"
MACRO_DIR=\"$MACRO_DIR\"
">${CONF_FILE}
}
function load_list()
{
#ARGS
# 1 => <STRING> : Name of the list
if [ -n "${1}" ]
then
clean_node
if [ -f ${LISTS_DIR}/${1} ]
then
for host in `cat ${LISTS_DIR}/${1}`
do
add_node $host
done
else
echo "Cannot find list ${arg}"
fi
else
syntax_error "load <list>"
fi
}
function display_variable()
{
#ARGS
# 1 => <STRING> : VARIABLE
$SETCOLOR_A
case "$1" in
"u" | "us" | "use" | "user")
echo "ssh user: $SSHUSER"
;;
"m" | "ma" | "mac" | "macr" | "macro" | "macros" )
echo "Registered macros:"
for f in `ls $MACROS_DIR/`
do
echo "=> $f"
done
echo
;;
"l" | "li" | "lis" | "list" | "lists" )
echo "Registered lists:"
for f in `ls $LISTS_DIR/`
do
echo "=> $f"
done
echo
;;
"n" | "no" | "node" | "node" | "nodes" )
display_nodes
;;
"serialize")
echo "serialize mode is $SERIALIZE"
;;
"o" | "out" | "output")
if [ -n "${OUTPUT}" ]
then
echo "Output log is directed to file ${OUTPUT}"
else
echo "No output log defined"
fi
;;
"t" | "te" | "tem" | "temp" | "tempo")
if [ -n "${TEMPO_SLICE}" ]
then
echo "tempo is every $TEMPO_SLICE node, wait $TEMPO_TIME"
else
echo "No tempo defined"
fi
;;
"col" | "colo" | "color" )
echo "color_a = ${COLOR_A}"
echo "color_b = ${COLOR_B}"
echo "color_c = ${COLOR_C}"
echo "color_normal = ${COLOR_NORMAL}"
;;
"con" | "conf")
echo "`$SETCOLOR_A`ssh user: `$SETCOLOR_C`$SSHUSER"
echo "`$SETCOLOR_A`ssh_timeout: `$SETCOLOR_C`${SSH_TIMEOUT}"
echo "`$SETCOLOR_A`serialize = `$SETCOLOR_C`$SERIALIZE"
echo "`$SETCOLOR_A`print_nodes = `$SETCOLOR_C`${PRINT_NODES}"
echo "`$SETCOLOR_A`print_summary = `$SETCOLOR_C`${PRINT_SUMMARY}"
echo "`$SETCOLOR_A`aggregate_output = `$SETCOLOR_C`${AGGREGATE_OUTPUT}"
echo "`$SETCOLOR_A`color_a = `$SETCOLOR_C`${COLOR_A}"
echo "`$SETCOLOR_A`color_b = `$SETCOLOR_C`${COLOR_B}"
echo "`$SETCOLOR_A`color_c = `$SETCOLOR_C`${COLOR_C}"
echo "`$SETCOLOR_A`color_normal = `$SETCOLOR_C`${COLOR_NORMAL}"
echo "`$SETCOLOR_A`strip_explore = `$SETCOLOR_C`${STRIP_EXPLORE}"
if [ -n "${TEMPO_SLICE}" ]
then
echo "`$SETCOLOR_A`Tempo is every `$SETCOLOR_C`$TEMPO_SLICE`$SETCOLOR_A` node(s), wait `$SETCOLOR_C`$TEMPO_TIME`$SETCOLOR_A` second(s)"
else
echo "`$SETCOLOR_C`No tempo defined"
fi
if [ -n "${OUTPUT}" ]
then
echo "`$SETCOLOR_A`Output log is directed to file `$SETCOLOR_C`${OUTPUT}"
else
echo "`$SETCOLOR_C`No output log defined"
fi
echo "`$SETCOLOR_A`lists dir set to `$SETCOLOR_C`${LISTS_DIR}"
echo "`$SETCOLOR_A`macros dir set to `$SETCOLOR_C`${MACROS_DIR}"
display_nodes
;;
*)
echo "Sorry.. I cannot show you this ;)"
;;
esac
$SETCOLOR_NORMAL
}
function parse_input()
{
local host=''
local cmd=''
local arg=''
local index=0
local maxnode=255
local arg2=''
local STRING=`echo ${1} | tr -s ' '`
cmd=`echo $STRING | awk '{print $1}'`
case "$cmd" in
"help" | "?")
display_help
;;
"pushkey")
arg=`echo $STRING | awk '{print $2}'`
for pkn in `echo $arg | tr ',' ' '`
do
push_key ${pkn}
done
;;
"explore")
arg=`echo $STRING | awk '{print $2}'`
explore_network $arg
;;
"!")
CMD=`echo $STRING | sed 's/! \(.*\)$/\1/'`
bash -c "$CMD"
;;
"CD")
arg=`echo $STRING | awk '{print $2}'`
echo $arg | grep -q '^/'
if [ $? -eq 0 ]
then
DISTANT_CD=$arg
else
DISTANT_CD=${DISTANT_CD}/${arg}
fi
;;
"cd")
arg=`echo $STRING | awk '{print $2}'`
cd "${arg}"
;;
"c" | "cl" | "cle" | "clea" | "clear" )
arg=`echo $STRING | awk '{print $2}'`
if [ ! -n "${arg}" ]
then
echo "Clear what ?? use ? for help"
fi
case "${arg}" in
"t" | "te" | "tem" | "temp" | "tempo" )
TEMPO_TIME=
TEMPO_SLICE=
;;
"l" | "lo" | "log" )
OUTPUT=
;;
"n" | "no" | "nod" | "node" | "nodes" )
clean_node
;;
* )
echo "Don't know what to clear... try ?"
;;
esac
;;
"se" | "set" )
arg=`echo $STRING | awk '{print $2}'`
arg2=`echo $STRING | awk '{print $3}'`
arg3=`echo $STRING | awk '{print $4}'`
if [ ! -n "${arg2}" ]
then
echo "Can't set $arg to nothing... use ? for help"
else
set_variable $arg $arg2 $arg3
fi
;;
"p" | "pu" | "put" | "r" | "ru" | "run" | "exe" | "exec" | "execu" | "execut" | "execute" | "g" | "ge" | "get" )
clean_run_node
arg=`echo $STRING | awk '{print $2}'`
arg2=`echo $STRING | tr -s ' ' | sed 's/[^ ]*[ ]*[^ ]*[ ]*\(.*\)$/\1/'`
if [ -n "$DISTANT_CD" ]
then
echo "cd $DISTANT_CD || exit 255 2>/dev/null ;" >${TMP_STR}
echo "${arg2}" >> ${TMP_STR}
else
echo "${arg2}" > ${TMP_STR}
fi
if [ ${arg} = "all" -o ${arg} = "a" -o ${arg} = "al" ]
then
for index in `seq 0 $maxnode`
do
runnodes[index]="${node[index]}"
done
else
local index=0
for v in `echo $arg | tr ',' ' '`
do
for w in `seq 0 $maxnode`
do
# does node matches a sed regex ?
M=`echo ${node[w]} | sed "s/$v//"`
if [ -z "$M" ] ; then
# if empty node, no need to add it here and increase index.
if [ -n "${node[w]}" ] ; then
runnodes[index]="${node[w]}"
index=$(( index + 1 ))
fi
fi
done
done
fi
local index=0
if [ ${PRINT_NODES} = "yes" ]
then
echo -ne "Nodes :"
for index in `seq 0 $maxnode`
do
if [ -n "${runnodes[index]}" ]
then
echo -ne " ${runnodes[index]}"
fi
done
echo
fi
if [ -n "${OUTPUT}" ]
then
echo "==>command launched at `date`. Input was :$STRING (${arg2})" >>${OUTPUT}
fi
echo -ne "Sending commands... "
MOVE_TO_COL="echo -en \\033[30G"
for index in `seq 0 $maxnode`
do
if [ -n "${runnodes[index]}" ]
then
SSH_ARGS="-T -o BatchMode=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no"
SCP_ARGS="-q -o BatchMode=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no"
if [ "${USE_SSH_TIMEOUT}" = "yes" ]
then
SSH_ARGS=${SSH_ARGS}" -o ConnectTimeout=$SSH_TIMEOUT "
SCP_ARGS=${SCP_ARGS}" -o ConnectTimeout=$SSH_TIMEOUT "
fi
if [ "${SERIALIZE}" = "on" ]
then
case "${cmd}" in
"p" | "pu" | "put")
bash -c "scp ${SCP_ARGS} $arg2 $SSHUSER@${runnodes[index]}: >$OUT_DIR/${runnodes[index]}2>$ERR_DIR/${runnodes[index]} ; echo \$? > $END_DIR/${runnodes[index]}"
$MOVE_TO_COL ; echo -ne " " ; $MOVE_TO_COL ; echo -ne "${runnodes[index]}"
;;
"r" | "ru" | "run" )
bash -c "cat ${TMP_STR} | ssh ${SSH_ARGS} $SSHUSER@${runnodes[index]} >$OUT_DIR/${runnodes[index]} 2>$ERR_DIR/${runnodes[index]} ; echo \$? > $END_DIR/${runnodes[index]}"
$MOVE_TO_COL ; echo -ne " " ; $MOVE_TO_COL ; echo -ne "${runnodes[index]}"
;;
"exe" | "exec" | "execu" | "execut" | "execute" )
bash -c "scp ${SCP_ARGS} ${MACROS_DIR}/$arg2 $SSHUSER@${runnodes[index]}: 2>$ERR_DIR/${runnodes[index]} ; ssh ${SSH_ARGS} $SSHUSER@${runnodes[index]} \"chmod 700 $arg2 ; ./$arg2 ; rm ./$arg2 \" >$OUT_DIR/${runnodes[index]} 2>$ERR_DIR/${runnodes[index]} ; echo \$? > $END_DIR/${runnodes[index]}"
$MOVE_TO_COL ; echo -ne " " ; $MOVE_TO_COL ; echo -ne "${runnodes[index]}"
;;
"g" | "ge" | "get" )
[ -d ${runnodes[index]} ] || mkdir ${runnodes[index]}
bash -c "scp ${SCP_ARGS} $SSHUSER@${runnodes[index]}:${arg2} ${runnodes[index]}/${arg2} >$OUT_DIR/${runnodes[index]} 2>$ERR_DIR/${runnodes[index]} ; echo \$? > $END_DIR/${runnodes[index]}" &
$MOVE_TO_COL ; echo -ne " " ; $MOVE_TO_COL ; echo -ne "${runnodes[index]}"
;;
esac
else
if [ -n "${TEMPO_SLICE}" ] && [ ${index} -ne 0 ]
then
if [ "$(( index % $TEMPO_SLICE ))" -eq 0 ]
then
sleep $TEMPO_TIME
fi
fi
case "${cmd}" in
"p" | "pu" | "put")
bash -c "scp ${SCP_ARGS} $arg2 $SSHUSER@${runnodes[index]}: >$OUT_DIR/${runnodes[index]} 2>$ERR_DIR/${runnodes[index]} ; echo \$? > $END_DIR/${runnodes[index]}" &
$MOVE_TO_COL ; echo -ne " " ; $MOVE_TO_COL ; echo -ne "${runnodes[index]}"
;;
"r" | "ru" | "run" )
bash -c "cat ${TMP_STR} | ssh ${SSH_ARGS} $SSHUSER@${runnodes[index]} >$OUT_DIR/${runnodes[index]} 2>$ERR_DIR/${runnodes[index]} ; echo \$? > $END_DIR/${runnodes[index]}" &
$MOVE_TO_COL ; echo -ne " " ; $MOVE_TO_COL ; echo -ne "${runnodes[index]}"
;;
"exe" | "exec" | "execu" | "execut" | "execute" )
bash -c "scp ${SCP_ARGS} ${MACROS_DIR}/$arg2 $SSHUSER@${runnodes[index]}: 2>$ERR_DIR/${runnodes[index]} ; ssh ${SSH_ARGS} $SSHUSER@${runnodes[index]} \"chmod 700 ./$arg2 ; ./$arg2 ; rm ./$arg2 \" >$OUT_DIR/${runnodes[index]} 2>$ERR_DIR/${runnodes[index]} ; echo \$? > $END_DIR/${runnodes[index]}" &
$MOVE_TO_COL ; echo -ne " " ; $MOVE_TO_COL ; echo -ne "${runnodes[index]}"
;;
"g" | "ge" | "get" )
[ -d ${runnodes[index]} ] || mkdir ${runnodes[index]}
bash -c "scp ${SCP_ARGS} $SSHUSER@${runnodes[index]}:${arg2} ${runnodes[index]}/${arg2} >$OUT_DIR/${runnodes[index]} 2>$ERR_DIR/${runnodes[index]} ; echo \$? > $END_DIR/${runnodes[index]}" &
$MOVE_TO_COL ; echo -ne " " ; $MOVE_TO_COL ; echo -ne "${runnodes[index]}"
;;
esac
fi
fi
done
# wait sons here. can/must be optimized.
# In fact, only if output is aggregated you need to wait for every node before displaying.
# If you don't aggregate, you can go faster and display as soon as one is finished.
PRINT_SUMMARY_ZERO=0
MOVE_TO_COL="echo -en \\033[0G"
for index in `seq 0 $maxnode`
do
if [ -n "${runnodes[index]}" ]
then
$MOVE_TO_COL ; echo -ne "Waiting ${runnodes[index]}... "
while [ ! -f ${END_DIR}/${runnodes[index]} ]
do
usleep 40000
done
retcode=`cat $END_DIR/${runnodes[index]}`
if [ $retcode -eq 0 ]
then
PRINT_SUMMARY_ZERO=$(( ${PRINT_SUMMARY_ZERO} + 1 ))
fi
fi
done
if [ $AGGREGATE_OUTPUT = "yes" ]; then
for index in `seq 0 $maxnode`
do
if [ -n "${runnodes[index]}" ]
then
$MOVE_TO_COL ; echo -ne "Aggregating ${runnodes[index]}... "
# Ok, let's try to agregate this output with previous nodes.
if [ ${index} -gt 0 ]
then
for index2 in `seq 0 $(( $index -1 ))`
do
if [ -n "${runnodes[index2]}" ] && [ -n "${runnodes[index]}" ]
then
diff "$OUT_DIR/${runnodes[index2]}" "$OUT_DIR/${runnodes[index]}" >/dev/null 2>/dev/null
SAME_1=$?
diff "$ERR_DIR/${runnodes[index2]}" "$ERR_DIR/${runnodes[index]}" >/dev/null 2>/dev/null
SAME_2=$?
if [ ${SAME_1} -eq 0 ] && [ ${SAME_2} -eq 0 ]
then
# erase index, move in index2
echo ${runnodes[index2]} | grep -q '^+'
if [ $? -eq 0 ]
then
echo -ne ",${runnodes[index]}">> "${AGGR_DIR}/${runnodes[index2]}"
else
mv "${END_DIR}/${runnodes[index2]}" "${END_DIR}/+${runnodes[index2]}"
mv "${OUT_DIR}/${runnodes[index2]}" "${OUT_DIR}/+${runnodes[index2]}"
mv "${ERR_DIR}/${runnodes[index2]}" "${ERR_DIR}/+${runnodes[index2]}"
echo -ne "${runnodes[index2]},${runnodes[index]}">> "${AGGR_DIR}/+${runnodes[index2]}"
runnodes[index2]="+${runnodes[index2]}"
fi
rm ${END_DIR}/${runnodes[index]} ${OUT_DIR}/${runnodes[index]} ${ERR_DIR}/${runnodes[index]}
runnodes[index]=""
fi
fi
done
fi
fi
done
fi
MOVE_TO_COL="echo -en \\033[0G"
$MOVE_TO_COL
echo "All done... "
for index in `seq 0 $maxnode`
do
if [ -n "${runnodes[index]}" ]
then
retcode=`cat $END_DIR/${runnodes[index]}`
$SETCOLOR_A
echo ${runnodes[index]} | grep -q '^+'
if [ $? -eq 0 ]
then
echo "=>>> `cat ${AGGR_DIR}/${runnodes[index]}` <<<= ($retcode)"
if [ -n "${OUTPUT}" ] ; then
echo "=>>> `cat ${AGGR_DIR}/${runnodes[index]}` <<<= ($retcode)" >> "$OUTPUT"
fi
else
echo "=> ${runnodes[index]} <= ($retcode)"
if [ -n "${OUTPUT}" ] ; then
echo "=> ${runnodes[index]} <= ($retcode)" >> "$OUTPUT"
fi
fi
$SETCOLOR_B
cat $OUT_DIR/${runnodes[index]}
$SETCOLOR_C
cat $ERR_DIR/${runnodes[index]}
$SETCOLOR_NORMAL
if [ -n "${OUTPUT}" ]
then
cat $OUT_DIR/${runnodes[index]} >> "$OUTPUT"
cat $ERR_DIR/${runnodes[index]} >> "$OUTPUT"
fi
fi
done
if [ "${PRINT_SUMMARY}" = "yes" ] ;then
$SETCOLOR_A
$ECHOLINE
echo
echo "${PRINT_SUMMARY_ZERO} Nodes completed command(s) successfully"
echo "Distant Current Directory : ${DISTANT_CD}"
$SETCOLOR_NORMAL
fi
bash -c "rm -f ${OUT_DIR}/* ${AGGR_DIR}/* ${ERR_DIR}/* ${END_DIR}/* ${TMP_STR} >/dev/null 2>&1"
;;
"exi" | "exit")
exit 0
;;
"w" | "wr" | "wri" | "writ" | "write" )
arg=`echo $STRING | awk '{print $2}'`
arg2=`echo $STRING | awk '{print $3}'`
case "${arg}" in
"l" | "li" | "lis" | "list" )
if [ -n "${arg2}" ]
then
echo -ne >${LISTS_DIR}/${arg2}
for index in `seq 0 $maxnode`
do
if [ -n "${node[index]}" ] ; then
echo ${node[index]} >>${LISTS_DIR}/${arg2}
fi
done
echo "${arg2} list saved."
else
echo "Syntax error: write list <list>"
fi
;;
"m" | "me" | "mem" | "c" | "co" | "con" | "conf" )
write_conf
;;
*)
syntax_error "write list <list> or write < mem | conf >"
;;
esac
;;
"l" | "lo" | "loa" | "load")
arg=`echo $STRING | awk '{print $2}'`
# look for list
load_list $arg
;;
"ed" | "edi" | "edit" )
arg=`echo $STRING | awk '{print $2}'`
case "$arg" in
"m" | "ma" | "mac" | "macr" | "macro" )
arg2=`echo $STRING | awk '{print $3}'`
if [ -n "${arg2}" ]
then
vi ${MACROS_DIR}/${arg2}
else
syntax_error "edit macro <name>"
fi
;;
"l" | "li" | "lis" | "list")
arg2=`echo $STRING | awk '{print $3}'`
if [ -n "${arg2}" ]
then
vi ${LISTS_DIR}/${arg2}
else
syntax_error "edit list <name>"
fi
;;
*)
;;
esac
;;
"a" | "ad" | "add")
arg=`echo $STRING | awk '{print $2}'`
case "$arg" in
"n" | "no" | "node" | "node")
arg2=`echo $STRING | awk '{print $3}'`
if [ -n "${arg2}" ]
then
add_node "${arg2}"
else
syntax_error "add node <name>"
fi
;;
"l" | "li" | "lis" | "list")
arg2=`echo $STRING | awk '{print $3}'`
if [ -n "${arg2}" ]
then
if [ -f ${LISTS_DIR}/${arg2} ]
then
for host in `cat ${LISTS_DIR}/${arg2}`
do
add_node $host
done
else
echo "Cannot find list ${arg2}"
fi
else
syntax_error "add list <list>"
fi
;;
*)
syntax_error "Can't add this.. don't know how !"
esac
;;
"d" | "de" | "del")
arg=`echo $STRING | awk '{print $2}'`
arg2=`echo $STRING | awk '{print $3}'`
case "$arg" in
"n" | "no" | "node" | "node")
if [ -n "${arg2}" ]
then
delete_node "${arg2}"
else
syntax_error "del node <name>"
fi
;;
"m" | "ma" | "mac" | "macr" | "macro" )
if [ -n "${arg2}" ]
then
rm "${MACROS_DIR}/${arg2}"
else
syntax_error "del macro <name>"
fi
;;
"l" | "li" | "lis" | "list" )
if [ -n "${arg2}" ]
then
rm "${LISTS_DIR}/${arg2}"
else
echo "Syntax: del list <name>"
fi
;;
*)
syntax_error "Can't delete this.. don't know how !"
esac
;;
"sh" | "sho" | "show" )
arg=`echo $STRING | awk '{print $2}'`
display_variable $arg
;;
*)
echo "Unrecognized command"
;;
esac
}
init_start
init_colors $COLOR_A $COLOR_B $COLOR_C $COLOR_NORMAL
while [ -n "${1}" ]
do
case "${1}" in
"-l")
shift
FILE_HOST_LIST="${1}"
shift
;;
"-d")
shift
DEBUG=1
;;
"-u")
shift
SSHUSER="${1}"
shift
;;
*)
syntax
exit -1
;;
esac
done
if [ -n "${FILE_HOST_LIST}" ]
then
for host in `cat ${FILE_HOST_LIST}`
do
add_node $host
done
fi
HISTFILE="${JPS_HISTORY}"
shopt -s histappend
shopt -s histreedit
shopt -s histverify
while [ true ]
do
history -r $HISTFILE
read -r -e -p ${JPSPROMPT} inputline
echo "${inputline}" >>$HISTFILE
parse_input "$inputline"
done