#!/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