开发者

Bash problem with eval, variables and quotes

I've been reading about quotes in bash here and everywhere else, but i got no help solving this problem.

The thing is, I have a little script for doing backups in a loop.

If I don't use eval then I have problems with $OPTIONS variable in rsync.

But if I do use eval then the problem goes to the variable $CURRENT_DIR...

rsync returns the following message: 'Unexpected local arg: /path/with'

I've tried every way of quoting the variable $CURRENT_DIR

CURRENT_DIR="/path/with spaces/backup"
DIR="dir_by_project"
f=":/home/project_in_server"
OPTIONS="-avr --exclude 'public_html/cms/cache/**'开发者_高级运维 --exclude 'public_html/cms/components/libraries/cmslib/cache/**' --delete"
eval rsync --delete-excluded -i $OPTIONS  root@example.com$f $CURRENT_DIR/xxx/$DIR/files

Is there any way that i can use the variable $CURRENT_DIR without problems caused by spaces?


eval rsync --delete-excluded -i $OPTIONS  root@example.com$f "\"$CURRENT_DIR/xxx/$DIR/files\""

command "some thing" executes command with one argument some thing. The quotes are parsed by the shell and arguments are setup as an array when executing the command. The command will see an argument as some thing without the quotes.

The eval command treats its arguments more or less as if they were typed into the shell. So, if you eval command "some thing", bash executes eval with two arguments: command and some thing (again the quotes are eaten while bash sets up the array of arguments). So, eval then acts as if you typed command some thing in the shell, which is not what you want.

What I did was simply to escape the quotes, so that bash passes literally "some thing" including the quotes to eval. eval then acts as if you typed command "some thing".

The outer quotes in my command is not strictly required, they're just habit. You could just as well use:

eval rsync --delete-excluded -i $OPTIONS  root@example.com$f \"$CURRENT_DIR/xxx/$DIR/files\"


Using eval is dangerous, and should be avoided whenever possible. In this case, the primary problem is that you're trying to define OPTIONS as containing multiple words, and bash variables don't handle this very well. There is a solution: put OPTIONS in an array, instead of a simple variable (and use double-quotes around all variable references, to keep spaces from getting treated as word separators).

CURRENT_DIR="/path/with spaces/backup"
DIR="dir_by_project"
f=":/home/project_in_server"
OPTIONS=(-avr --exclude 'public_html/cms/cache/**' --exclude 'public_html/cms/components/libraries/cmslib/cache/**' --delete)
rsync --delete-excluded -i "${OPTIONS[@]}"  "root@example.com$f" "$CURRENT_DIR/xxx/$DIR/files"


BASH FAQ entry #50: "I'm trying to put a command in a variable, but the complex cases always fail!"


Generic reusable solution

While understanding how to correctly quote things is important, for ease of use and to prevent slip-ups I prefer to use a function:

The following keeps spaces in arguments by quoting each element of array:

function token_quote {
  local quoted=()
  for token; do
    quoted+=( "$(printf '%q' "$token")" )
  done
  printf '%s\n' "${quoted[*]}"
}

Example usage:

$ token_quote token 'single token' token
token single\ token token

Above, note the single token's space is quoted as \.

$ set $(token_quote token 'single token' token)
$ eval printf '%s\\n' "$@"
token
single token
token
$

This shows that the tokens are indeed kept separate.


Given some untrusted user input:

% input="Trying to hack you; date"

Construct a command to eval:

% cmd=(echo "User gave:" "$input")

Eval it, with seemingly correct quoting:

% eval "$(echo "${cmd[@]}")"
User gave: Trying to hack you
Thu Sep 27 20:41:31 +07 2018

Note you were hacked. date was executed rather than being printed literally.

Instead with token_quote():

% eval "$(token_quote "${cmd[@]}")"
User gave: Trying to hack you; date
%

eval isn't evil - it's just misunderstood :)


I agree with Gordon

You have no need for eval in this case (you are not forming a variable name from variables, or otherwise making an expression on the fly)

And and you want to double-quote all variable referensces that COULD have spaces that you would want to preserve

But Another good habit is to always refrence variables with {} ...

  "${CURRENT_DIR}" 

instead of

  $CURRENT_DIR

This removes any name ambiguity


I know probably ypu have used it already, but, what about single quotes? (this type ' ' ) ?


You need to escape space in CURRENT_DIR="/path/with\ spaces/backup" if this doesn't work then put double backslash CURRENT_DIR="/path/with\\ spaces/backup"

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜