2

I made this script for a course. It executes commands through ssh from the arguments of the script, on a number of remote servers specified in a file:

#!/bin/bash

# The server file. Can be changed with the -f argument
SERVER_FILE='/vagrant/servers'

# The function to check if the chosen SERVER_FILE exists
filecheck() {
if [[ ! -e $SERVER_FILE ]]; then
  echo "The file $SERVER_FILE does not exist." >&2
  exit 1
fi
}

# The usage statement
usage() {
  echo "usage $0 -vsn -f FILE 'COMMAND'"
  echo "  -v Verbose mode"
  echo "  -s Run command as sudo on remote server"
  echo "  -n Dry run, commands not actually executed"
  echo "  -f FILE Selects a different file other than /vagrant/servers"
  exit 1
}

# The verbose mode text things
say() {
  if [[ $VERBOSE = 'true' ]]; then
    echo "$@"
  fi
}

# The ssh command
sshing() {
  ssh -o ConnectTimeout=2 $SERVER $@
}

# User executing the command should not be root
if [[ $UID -eq 0 ]]; then
  echo "You should not execute this script with sudo or as root" >&2
  echo "Use the -s argument if you want sudo powers" >&2
  exit 1
fi

# DRYMODE is sshing by Default
DRYMODE='sshing'

#check to see if file SERVER_FILE exists
filecheck

# The options for the script
while getopts vsnf: OPTION; do
  case $OPTION in
    v)
      echo "Verbose mode on"
      VERBOSE='true'
      ;;
    s)
      say "Sudo mode"
      SUDO='sudo'
      ;;
    n)
      say "Dry run mode"
      DRYMODE='echo'
      DRYRUN='DRY RUN: '
      echo "DRY RUN MODE ON: "
      echo
      ;;
    f)
      say "Different file mode"
      SERVER_FILE=${OPTARG}
      #check to see if file SERVER_FILE exists
      filecheck
      ;;
    *)
      usage
      ;;
  esac
done

echo

# shifts so that the options are removed from the list of arguments
shift $((OPTIND-1))

#Set a variable for the rest of the arguments, as a command
COMMAND="${@}"

# Checks if the user provided any arguments apart from the optinos
if [[ $# -eq 0 ]]; then
  usage
  exit 1
fi

# Executes the commands
for SERVER in $(cat ${SERVER_FILE}); do
  say "Executing ${COMMAND} on ${SERVER}:"
  $DRYMODE $DRYRUN $SUDO ${COMMAND} 2> /dev/null
  CMDEX=$?
  # if the exit status is 255, something is wrong with the server or is unreachable
  if [[ $CMDEX -eq 255 ]]; then
    echo "The server you're trying to reach does not exist or is unreachable. Aborting." >&2
    exit 1
  fi
  # if the exit status is non 0 and non 255, something is wrong with the command
  if [[ $CMDEX -ne 0 ]]; then
    echo "Invalid command ${COMMAND} or wrong syntax. Aborting." >&2
    exit 1
    # if the exit status is non 0 and non 255, something is wrong with the command
  fi
  say "Command ${COMMAND} executed successfuly."
done
exit 0

And it works perfectly for simple commands (like ls, ps, and even adduser test), but it just breaks if I give it any command that would include a double quote UNLESS I single quote the whole command.

Now I don't know if it's a bug in my code or something, but I cannot pipe commands through this.

So this command does not work:

[vagrant@admin01 vagrant]$ ./run-everywhere.sh -sv 'echo 1 | passwd --stdin test4'

If I escape the pipe with \| it just literally writes it as \|. This other command also doesn't work:

[vagrant@admin01 vagrant]$ ./run-everywhere.sh -sv 'echo "1" | sha256sum > file1'

EDIT:

I found the problem with piping not working: I have to write sudo AFTER the pipe as well if a command needs sudo priviledges. This one works this way:

[vagrant@admin01 vagrant]$ ./run-everywhere.sh -sv 'echo 1 | sudo passwd --stdin test4'

I still cannot redirect though.

  • Related: unix.stackexchange.com/a/414640/117549 – Jeff Schaller Aug 27 '18 at 12:43
  • I don't see how that's related, or helpful to me. Could you please explain it a bit more clear? – iamAguest Aug 27 '18 at 12:46
  • You're not quoting your variables properly. Paste your code into shellcheck.net for more details. – glenn jackman Aug 27 '18 at 13:12
  • @glenn that was not the problem. After I fixed it, I still couldn't use redirects. Apparently I have no permission, even though I'm running it as root. – iamAguest Aug 27 '18 at 13:21
  • What and how did you fix? Try to double quote the ${COMMAND}. – RudiC Aug 27 '18 at 13:31
0

Try this:

sshing () {
  ssh -o ConnectTimeout=2 "$SERVER" "$@"
  # ................................^..^ crucial quotes
}
# ...
cmd="$*"
# ...
while read -r SERVER; do
  say "Executing ${COMMAND} on ${SERVER}:"
  $DRYMODE $DRYRUN $SUDO sh -c "${COMMAND}" 2> /dev/null
  # .....................11111.2..........2
  # 1. run with a shell to enable redirections and pipe
  # 2. crucial quotes
  # ...
done < "$SERVER_FILE"

Running the command inside a shell with sudo will allow the entire pipeline to execute with elevated permissions.

Also, you should get out of the habit of using ALLCAPS variable names. One day you'll accidentally overwrite PATH and then wonder why your script is broken.

  • Looking at your code, I will bet that the lack of quotes is exactly the problem. Do you have a file named "file" on your local machine? – glenn jackman Aug 27 '18 at 13:33
0

Found the problem. If I ran the script with sudo powers to touch some files on it's own, it would create the files under root's name and group, which meant I had no permissions on that file.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.