Inconsistent Arguments with Remote Script over SSH


While working on a new bash script, I discovered a pretty annoying difference between Kinsta and WP Engine’s SSH access. It involves sending arguments to a remote script. That’s something which is core to most of the scripts I write, like within my migration script. To demonstrate the bug I’ll start with a sample script named arguments.sh.

#!/bin/bash

#
#   Arguments sample script
#
#   `arugments.sh --domain=<domain> --id=<id> --author=<author-name> --author_uri=<author_uri>`
#

# Loop through all arguments and output
for arg in "$@"; do
   echo "Arg: $arg"
done

So nothing fancy, just a simple script which will output any argument you supply. The big quirk is how Kinsta and WP Engine behave when running this script remotely via over SSH as shown here.

ssh sitename@sitename.ssh.wpengine.net "bash -s -- --domain=anchor.host --author=\"Austin Ginder\"" < arguments.sh

WP Engine outputs broken:

Arg: --domain=anchor.host
Arg: --author=Austin
Arg: Ginder

Kinsta outputs properly:

Arg: --domain=anchor.host
Arg: --author=Austin Ginder

It’s appears WP Engine’s SSH is expanding all arguments which removes the quotes and breaks apart any argument values containing a space. This can be fixed for WP Engine by double wrapping the arguments like this.

ssh sitename@sitename.ssh.wpengine.net "bash -s -- --domain=anchor.host --author=\\\"\"Austin Ginder\\\"\"" < arguments.sh

However on Kinsta this means the values will be include the extra quotes.

Arg: --domain=anchor.host
Arg: --author="Austin Ginder"

In order to produce consistent results, my workaround is to double-wrap the quotes and remove any extra quotes from the argument values if found. So the full script with argument processing would look like this.

#!/bin/bash

#
#   Arguments sample script
#
#   `arugments.sh --domain=<domain> --id=<id> --author=<author-name> --author_uri=<author_uri>`
#

# Loop through arguments and separate regular arguments from flags
for arg in "$@"; do

  # Add to arguments array. (Does not starts with "--")
  if [[ $arg != --* ]]; then
    count=1+${#arguments[*]}
    arguments[$count]=$arg
    continue
  fi

  # Remove leading "--"
  flag_name=$( echo $arg | cut -c 3- )

  # Add to flags array
  count=1+${#flags[*]}
  flags[$count]=$arg

  # Process flags without data (Assign to variable)
  if [[ $arg != *"="* ]]; then
    flag_name=${flag_name//-/_}
    declare "$flag_name"=true
  fi

  # Process flags with data (Assign to variable)
  if [[ $arg == *"="* ]]; then
    flag_value=$( echo $flag_name | perl -n -e '/.+?=(.+)/&& print $1' ) # extract value
    flag_name=$( echo $flag_name | perl -n -e '/(.+?)=.+/&& print $1' ) # extract name
    flag_name=${flag_name/-/_}

    # Remove first and last quote if found
    flag_value="${flag_value%\"}"
    flag_value="${flag_value#\"}"

    declare "$flag_name"="$flag_value"
    continue
  fi

done

echo "Domain: $domain"
echo "Author: $author"

This script will now output the same thing on both platforms.

Domain: anchor.host
Author: Austin Ginder

You might be thinking that double wrapping everything seems like overkill. Well, it’s not a big deal if you use an SSH wrapper script. With my toolkit CaptainCore here are some example of commands which utilizes an SSH wrapper. Under the hood all arguments being sent to the remote are double wrapped in order to play nice with Kinsta and WP Engine.

captaincore ssh sitename --script=deploy-fathom --tracker=<tracker-domain> --id=<site-id> --branding_author=<captaincore_branding_author> --branding_author_uri=<captaincore_branding_author_uri> --branding_slug=<captaincore_branding_slug>
captaincore ssh sitename --script=deploy-mailgun --key=<key> --domain=<domain>
captaincore ssh sitename --script=verify-google-analytics-takeover --verifycode=<verifycode> --uacode=<uacode> --email=<email>