How can I properly escape bash variables when bein

2019-06-01 09:28发布

问题:

I have the following code:

set -o xtrace
openDirectory ()
{
    lxterminal --command="zsh -c 'cd "$1"; zsh -i' "
}

# Handle argument.
if [ "$@" ]
then
  openDirectory ~/Projects/Clients/"@1"
  cd $1
fi

This fails if there is a space in the argument passed.

lxterminal '--command=zsh -c '\''cd /home/chris/Projects/Clients/Test' - 'Space; zsh -i'\'' '
cd Test - Space
cd: too many arguments

How can I properly escape this? Is this even feasible to be done with something like bash?

回答1:

Assuming the enclosing shell really is bash (as the question is tagged), you can use printf -v varname %q "$var" to store a safely quoted instance of the value of var into the variable named varname, as such:

openDirectory()
{
    local cd_cmd cmd
    printf -v cd_cmd '%q ' cd "$1"
    printf -v cmd '%q ' zsh -c "$cd_cmd && zsh -i"
    lxterminal --command="$cmd"
}

printf '%q' evaluates to a version of the given string which will, when eval'ed by bash (or any shell with equivalent semantics), evaluate to the original literal value.

To explain why this is done that way:

  • In the above, all quote characters included in the code itself are syntactic rather than literal: We're depending on printf %q for all quoting and escaping. This is a best-practice approach: Using literal quotes means that literals inside the data being generated are able to escape those quotes.
  • Running shell directives such as && through shell quoting will make them into literal data. Thus, we want the && to be literal data rather than syntax when we're passing it as an argument to -c, but not literal data when handled by the remote shell -- this is why it's only present in the last quoting pass.


回答2:

Even though a good suggestion was made in the comments, as a general way to deal with such issues you could write it this way :

set -o xtrace
openDirectory ()
{
    local safe_path
    printf -v safe_path "%q" "$1"
    lxterminal --command="zsh -c 'cd $safe_path; zsh -i' "
}

For a better way to prepare the command to be passed as argument to lxterminal, please see CharlesDuffy's answer.

As for the rest of your code, I would probably use the following :

# Handle argument.
if
  [ -d  "$1" ]
then
  openDirectory ~/Projects/Clients/"@1"
  cd "$1"
fi

Using "$@" as the if condition could yield 0 (true) and execute the body if argument 1 exists but is empty (null string), but a following argument is not. I I assume this is not what you want.

I am unsure if "@1" is a typo or if it really is intended to mean a directory named "@1" inside your "Projects/Clients" directory. But the "$1" argument to cd should be quoted, for sure.