#!/bin/bash parssh () { (( $# )) || { _parssh_usage return $? } local _parssh_origopts=$- set -m local _parssh_outfun=_parssh_out local _parssh_prepend_host=true local _parssh_ssh=_parssh_ssh while [[ "$1" == -* ]]; do case ${1#-} in [0-9]*) [[ "${1#-}" != *[^0-9]* ]] && { local _parssh_concurrency=${1#-} } ;; s|-servers) [[ -r "$2" ]] || { echo "'$2': invalid file name or permissions issue" return 99 } local _parssh_servers="$2" shift ;; r|-rinput) [[ -r "$2" ]] || { echo "'$2': invalid file name or permissions issue" return 98 } local _parssh_rinput="$2" local _parssh_ssh=_parssh_ssh_rinput shift ;; b|-bare) unset _parssh_prepend_host ;; o|-out) touch "${2}.{out,err}" || { echo "'${2}.{out,err}': unable to write / modify file" return 97 } local _parssh_outprefix="$2" local _parssh_outfun=_parssh_out_redirect shift ;; t|-tee) touch "${2}.{out,err}" || { echo "'${2}.{out,err}': unable to write / modify file" return 96 } local _parssh_outprefix="$2" local _parssh_outfun=_parssh_out_copy shift ;; h|-help) _parssh_usage return $? ;; esac shift done [[ -z "$_parssh_servers" ]] && { exec 9<&0 } || { exec 9<"$_parssh_servers" } while read host; do while (( $(jobs -pr | wc -l) >= ${_parssh_concurrency:-4} )); do sleep 1 done $_parssh_ssh "$@" 2> >(_parssh_host_prepend >&2) |\ _parssh_host_prepend & done <&9 2> >($_parssh_outfun err >&2) |\ $_parssh_outfun out wait exec 9>&- [[ "${_parssh_origopts//[^m]/}" == "m" ]] || set +m } _parssh_ssh () { ssh -no StrictHostKeyChecking=no $host "$@" } _parssh_ssh_rinput () { ssh -To StrictHostKeyChecking=no $host "$@" < "$_parssh_rinput" } _parssh_host_prepend () while read -r; do printf "${_parssh_prepend_host+$host: }%s\n" "$REPLY" done _parssh_out () { cat - } _parssh_out_redirect () { local _parssh_out_stream="$1" cat - > "${_parssh_outprefix}.${_parssh_out_stream}" } _parssh_out_copy () { local _parssh_out_stream="$1" tee "${_parssh_outprefix}.${_parssh_out_stream}" } _parssh_usage () { printf ' %s\n'\ "parssh: Parallel SSH orchestration in a Bash session."\ ""\ "SYNOPSIS"\ "parssh [-NUM] [-r|--rinput FILE] [-s|--servers FILE] [-b|--bare] [COMMANDS] [< SERVERS]"\ ""\ "DESCRIPTION"\ " -NUM (default: 4)"\ " Number of concurrent ssh connections to maintain. E.g. '-40'."\ " -r FILE, --rinput FILE (default: unset/inactive)"\ " File to send as STDIN redirection on remote servers."\ " (Can be used as replacement for or in conjunction with COMMANDS)."\ " -s FILE, --servers FILE (default: unset/inactive)"\ " File to use as list of servers to run COMMANDS on."\ " (Cannot be used in conjunction with a server list on STDIN)."\ " -b, --bare (default: unset/inactive)"\ " Disable prepending of hostname to each output line returned by COMMANDS on SERVERS."\ " -o PREFIX, --out PREFIX (default: unset/inactive)"\ " Redirect STDOUT / STDERR to PREFIX.out / PREFIX.err, respectively."\ " -t PREFIX, --tee PREFIX (default: unset/inactive)"\ " Copy STDOUT / STDERR to PREFIX.out / PREFIX.err, respectively."\ " COMMANDS"\ " The list of commands to be executed remotely by SSH on each SERVER."\ " < SERVERS"\ " Unless '-s' flag is used, STDIN will be used as list of remote servers."\ " (Deliniated by whitespace)." return 1 }