开发者

Prevent ssh from breaking up shell script parameters

I have a script, which is essentially a wrapper around an executable by the same name on a different machine. For the sake of example, i'll wrap printf here. My current script looks like this:

#!/bin/bash
ssh user@ho开发者_StackOverflow中文版stname.tld. printf "$@"

Unfortunately, this breaks when one of the arguments contains a space, e.g. i'd expect the following commands to give identical outputs.:

~$ ./wrap_printf "%s_%s" "hello world" "1"
hello_world1_
~$ printf "%s_%s" "hello world" "1"
hello world_1

The problem gets even worse when (escaped) newlines are involved. How would I properly escape my arguments here?


Based on the answer from Peter Lyons, but also allow quotes inside arguments:

#!/bin/bash
QUOTE_ARGS=''
for ARG in "$@"
do
  ARG=$(printf "%q" "$ARG")
  QUOTE_ARGS="${QUOTE_ARGS} $ARG"
done

ssh user@hostname.tld. "printf ${QUOTE_ARGS}"

This works for everything i've tested so far, except newlines:

$ /tmp/wrap_printf "[-%s-]" "hello'\$t\""
[-hello'$t"-]


#!/bin/sh
QUOTE_ARGS=''
for ARG in "$@"
do
  QUOTE_ARGS="${QUOTE_ARGS} '${ARG}'"
done
ssh user@hostname.tld. "${QUOTE_ARGS}"

This works for spaces. It doesn't work if the argument has an embedded single quote.


Getting quoting right is pretty hard and doing it in bash (in a general and robust way) almost impossible.

Use Perl:

#!/usr/bin/perl
use Net::OpenSSH;
my $ssh = Net::OpenSSH->new('user@hostname');
$ssh->system('printf', @ARGV);


Based on the answers from Koert and Peter Lyons, here a wrapper for ssh; i call it "sshsystem". (also available at https://gist.github.com/4672115)

#!/bin/bash

# quote command in ssh call to prevent remote side from expanding any arguments
# uses bash printf %q for quoting - no idea how compatible this is with other shells.
# http://stackoverflow.com/questions/6592376/prevent-ssh-from-breaking-up-shell-script-parameters

sshargs=()

while (( $# > 0 )); do
    case "$1" in
    -[1246AaCfgKkMNnqsTtVvXxYy])
        # simple argument
        sshargs+=("$1")
        shift
        ;;
    -[bcDeFIiLlmOopRSWw])
        # argument with parameter
        sshargs+=("$1")
        shift
        if (( $# == 0 )); then
            echo "missing second part of long argument" >&2
            exit 99
        fi
        sshargs+=("$1")
        shift
        ;;
    -[bcDeFIiLlmOopRSWw]*)
        # argument with parameter appended without space
        sshargs+=("$1")
        shift
        ;;
    --)
        # end of arguments
        sshargs+=("$1")
        shift
        break
        ;;
    -*)
        echo "unrecognized argument: '$1'" >&2
        exit 99
        ;;
    *)
        # end of arguments
        break
        ;;
    esac
done


# user@host
sshargs+=("$1")
shift

# command - quote
if (( $# > 0 )); then
    # no need to make COMMAND an array - ssh will merge it anyway
    COMMAND=
    while (( $# > 0 )); do
        arg=$(printf "%q" "$1")
        COMMAND="${COMMAND} ${arg}"
        shift
    done
    sshargs+=("${COMMAND}")
fi

exec ssh "${sshargs[@]}"


The easiest and quickest is to just use Bash's Quoting Parameter Transformation: ${parameter@Q}. This can automatically applied during array expansion with ${array[@]@Q}, but when using the builtin argument array, the name and the brackets are dropped, so it becomes ${@@Q}. Therefore the original script only needs 4 characters added to it to work.

#!/bin/bash
ssh user@hostname.tld. printf "${@@Q}"

Now any escaping will work, even terminal colors like this:

./wrap_printf "%s\e[39m\e[49m\n" $'\e[30m\e[42mBlack on Green' "Just Normal Text"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜