1

I've created a shell script that has the SSH address as the first argument, then has one or more arguments afterwards. The script is capable of working with two arguments or less (the address and the single argument), but it is not able to work properly after. Below is my code to get a better idea.

ssh.sh $1 << EOF
$(typeset -f sr_single "$@")
if [ "$#" -eq 2 ]; then
    echo $2
    sr_single $2
elif [ "$#" -lt 2 ]; then
    echo "Needs at least two arguments: serial number and argument(s)"
else
    echo "${@:2}"
    for i in "${@:2}"; do
        echo "'" $i "'"
        sr_single $i
    done
fi
EOF

Below is what it returns if I call the function "sr.sh [email protected] -l"

-l

And below is what it returns when I call "sr.sh [email protected] -l -v"

-l -v
' '

My question is how is this function not getting the second variable and the ones after on this one, where it seems to be working properly in the rest of the program? Thanks

4
  • What's the purpose of $(typeset -f sr_single "$@")? It's strange that you have a command substitution without assigning the output to anything. Commented Aug 20, 2018 at 21:20
  • 1
    @BenjaminW., that's copying the local definition into the heredoc. Commented Aug 20, 2018 at 21:30
  • ...however, putting code literals in heredocs is a very error-prone business. Much better to use the same practice used for sr_single to pass the entire code. And, of course, fix your quoting -- which is to say, remove every single unquoted expansion. Commented Aug 20, 2018 at 21:31
  • BTW -- the original code here had some serious security vulnerabilities. If someone called your script with ./yourscript host _ '$(rm -rf ~)', the $2 expansion inside the heredoc would be substituting that as code, with the results one might expect. Always, always try to keep code and data out-of-band from each other -- and when you can't, printf '%q' is your friend. Commented Aug 20, 2018 at 21:44

1 Answer 1

2

To do this with less hair loss, define all your code in functions, like so:

rmt_main() {
  if (( $# == 2 )); then
    printf 'Exactly arguments received; $2 is: %q\n' "$2" >&2
  elif (( $# < 2 )); then
    echo 'Error: Needs at least two arguments' >&2
  else
    otherfunc "${@:2}"
  fi
}

otherfunc() {
  echo "Otherfunc called with arguments:" >&2
  printf '%q\n' "$@"
}

...after which, calling can look like:

# generate an eval-safe string containing your arguments
printf -v args_str '%q ' "$@"

# explicitly invoke bash, so we don't need to worry about whether our escaping is POSIX-y
ssh "$1" 'bash -s' <<EOF
$(typeset -f rmt_main otherfunc) # emit function definitions
rmt_main $args_str               # and call rmt_main with the eval-safe argument list
EOF

Note that the only contents we're expanding inside the heredoc are generated by the local shell in a form guaranteed to be correctly escaped to parse as code (by the remote shell). We are not under any circumstances expanding data (like command-line arguments) into the heredoc in unescaped form, and all our functions use completely conventional quoting (which is to say that all parameter expansions inside the function definitions are quoted).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.