The many circles of quoting hell

When it comes to shell programming quotes and escaped quotes and escaped escapes seem to offer a limitless pit of fun. At times it seems nearly insurmountable and depending on whether you are running interactively or from a script.

Recently I had the opportunity to assist in some bash shell scripting. The issue was largely based on trying to get smbclient to run via some options that were being passed and parsed into a script.

The fun thing was that the command, as it was built in the script, would run perfectly when cut and pasted into a terminal, but within the script it would generate an error, that seemed to point to some quoting issues.

After much trial and error I remembered having seen something similar in the past, where I was trying to build a complex command line which also required nested double and single quotes as well as pound signs.

In essence I build the command as follows (stupid things ignored):


LS_OPTIONS="-ltr"
COMMAND="ls"
COMMAND="$COMMAND $LS_OPTIONS"
FILE_NAME="a long file with spaces in them"
COMMAND="$COMMAND \"$FILE_NAME\""
PIPE_COMMAND_1="cut -f 7 -d ' '"
PIPE_COMMAND_2="sed -e 's/^/\"/g; s/$/\"/g'"
COMMAND="$COMMAND | $PIPE_COMMAND_1 | $PIPE_COMMAND_2"
echo $COMMAND
$COMMAND

This will fail with:

ls -ltr "a long file with spaces in them" | cut -f 7 -d ' ' | sed -e 's/^/"/g; s/$/"/g'
ls: invalid option -- e
Try `ls --help' for more information.

If you try and cut and paste the echoed command it succeeds, just not within the script.

However if $COMMAND is echoed to a new shell as follows, it works (only the last line changes):

LS_OPTIONS="-ltr"
COMMAND="ls"
COMMAND="$COMMAND $LS_OPTIONS"
FILE_NAME="a long file with spaces in them"
COMMAND="$COMMAND \"$FILE_NAME\""
PIPE_COMMAND_1="cut -f 7 -d ' '"
PIPE_COMMAND_2="sed -e 's/^/\"/g; s/$/\"/g'"
COMMAND="$COMMAND | $PIPE_COMMAND_1 | $PIPE_COMMAND_2"
echo $COMMAND
echo $COMMAND | sh

and you will see:

ls -ltr "a long file with spaces in them" | cut -f 7 -d ' ' | sed -e 's/^/"/g; s/$/"/g'
"09:45"

I did spend some time trying to figure out why this works, but came up inconclusive. In the meantime, I've used this trick a few times now.

Hope this helps someone else get out of the shell pit.

\@matthias

Comments

It's worse with environment variables

This is exactly the kind of thing that I would need to figure out with LS_OPTIONS environment variable. For example, if I have this in .bashrc:
export LS_OPTIONS="--time-style=+'%d.%m.%Y %T'"

This is what happens:
$ ls -l $LS_OPTIONS
ls: cannot access %T': No such file or directory

Obviously that whitespace causes problems when exported into an environment variable, but it works perfectly when run like this:
$ ls -l --time-style=+'%d.%m.%Y %T'

But using double quotes with the variable works:
$ ls -l "$LS_OPTIONS"

That doesn't help, because I have more than one option in .bashrc:
export LS_OPT1="--time-style=+'%d.%m.%Y %T'"
export LS_OPTIONS="${LS_OPTIONS} --color=auto ${LS_OPT1}"

When this is sourced, it breaks again:
$ ls -l "$LS_OPTIONS"
ls: cannot access --color=auto --time-style=+%d.%m.%Y %T: No such file or directory

As you can see, it doesn't help if you export the time-style parameter as a separate variable and then use it in the LS_OPTIONS variable. Escaping the whitespace character with \ doesn't help either. I haven't found a solution to this so far. These are the sorts of things make me want to kill someone.