0

I am trying to write a bash script which will take the input in form of arguments and execute certain commands (provided as arguments) on the remote hosts which will be provided via txt file

For eg: there is text file /var/tmp/hosts.txt which have hostnames which we will be executing a set of commands. When I run the script (command.sh) I should be able to run commands on the remote hosts provided in the hosts.txt file

For instance: Hosts file - hosts.txt and Command to be executed is:

lscpu  | grep "Model name" | cut -d: -f2 | awk '{$1=$1};1'

I should be able to execute in this way:

command.sh -f hosts.txt -c "lscpu  | grep "Model name" | cut -d: -f2 | awk '{$1=$1};1'"

and it should run the command provided in the -c argument on hosts xyz after doing an ssh

I was able to get single line commands working (whoami, hostname date .. ) but when it comes to combination of commands, I failed to achieve that because it takes them as different set of arguments.

\#!/bin/bash

usage () {
    echo "Usage: $0 \<Hostfile\>"
    exit -1
}

id="rm08397"

while getopts ":c:f:" option; do

    case "$option" in
        f) file_arg=$OPTARG ;;
        c) command_arg="$OPTARG" ;;
    esac
done

if \[ "$file_arg" != "" \]; then
    HOSTFILE="$2"
fi

if \[ $command_arg != "" \]; then
    cmd="$command_arg"
else
    cmd=$4
fi

for host in `cat $HOSTFILE`
do
    echo -n "$host#"
    ssh $id@$host $cmd
done
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
Noah
  • 35
  • 4
  • 3
    If you take a minute to read the formatting help and then properly format your script as a code sample it will be easier for people to read it and help you out. – larsks Mar 28 '22 at 21:45
  • @larsks I think there is some problem with SO that is causing extra backslashes to appear in code excerpts. I've seen it happen a number of times in the past few days. – siride Mar 29 '22 at 01:49
  • I formatted the script and still it gives cat as the argument, because it has a space after the argument. Even when I try to execute the script as ./ch.sh -f tes2.txt -c "cat /etc/redhat-release" it still gives cat as the output for the $cmd variable – Noah Mar 29 '22 at 03:00
  • `#!/bin/bash usage () { echo "Usage: $0 " exit -1 } id="rm08397" while getopts ":c:f:" option; do case "$option" in f) file_arg=$OPTARG ;; c) command_arg="$OPTARG" ;; esac done if [ "$file_arg" != "" ]; then HOSTFILE="$2" fi # while (($#));do # cmd=$4 # shift # cmd=$@ # cmd2="${cmd}" if [ $command_arg != "" ]; then cmd="$command_arg" fi # echo $cmd2 for host in `cat $HOSTFILE` do echo $cmd echo -n "$host#" ssh $id@$host /bin/bash -c $cmd done ` – Noah Mar 29 '22 at 03:02

2 Answers2

0

There are many ways to try to do this, but it's tough because of word splitting and quoting.

I would recommend running ssh like this:

ssh $id@$host /bin/bash -c "$cmd"

This let's remote bash take care of parsing the command.

In the comment below you are saying that it is not capturing the whole command after -c. The problem is not with the script but with how you invoke it. You use double quotes for the argument to -c, but within that argument you use more double quotes. Those cause the parser to end the command early. If you print out the command variable in the script you will see where it stops. To fix this, just put backslashes before any double quotes in the command string, or use single quotes there instead.

command.sh -f hosts.txt -c "lscpu | grep \"Model name\" | cut -d: -f2 | awk '{$1=$1};1'"
siride
  • 200,666
  • 4
  • 41
  • 62
  • Hey Siride, Thank you for replying. I guess the issue is how can I script in a way that anything passed after the argument -c. It should be saved in a single variable (despite of the number of string characters) and then later can be used to pass in the for loop where hostnames are being collected from the file if [ $command_arg != "" ]; then cmd="$command_arg" fi If you can help me figure out a way to parse the argument into a variable and, irrespective of the line spaces – Noah Mar 29 '22 at 02:54
  • @Noah see my edit – siride Mar 29 '22 at 12:18
0

Transfering work to the remote via ssh is uterrly hard. Do not care about proper double-quoting. Properly serialize data nad code.

Create a function to run the remote commands.

func() {  ....; }

Then reformat your program to properly serialize the function to the remote and run it.

...
-c) command_arg=$OPTARG; ;;
...
while IFS= read -r host; do
    echo -n "$host#"
    # magic
    # run bash -c on the remote side
    # with `declare -f <the_function>` serialization of the function
    # followed by `; <the_function>` actually exeucting the function
    ssh "$id@$host" "$(printf "%q " bash -c "$(declare -f "$command_arg"); $command_arg")"
done <"$hostfile"

Then you can do:

func() { lscpu  | grep "Model name" | cut -d: -f2 | awk '{$1=$1};1'; }
export -f func
./command.sh -f hosts -c func

Note that you do not have to care about properly \"\" double quoting your command. Remember to check your script with shellcheck. Do not use backticks.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111