0
$ md="-l /tmp/test/my dir"
$ ls "$md"
ls: invalid option -- ' '
Try 'ls --help' for more information.

$ md="-l \"/tmp/test/my dir\""
$ ls "$md"
ls: invalid option -- ' '
Try 'ls --help' for more information.

I was wondering why both don't work? Thanks. Note that the value of the variable contains

  • an option -l
  • a pathname argument which contains a space
  • a space which separates the above two.

My actual problem is: I wrote a script myscript.sh

#! /bin/bash

old_dest=""
while getopts ":l:t" opt; do
    case $opt in
        l)
            old_dest="--link-dest \"$OPTARG\""
            ;;
    esac
done

rsync -a --delete "$old_dest" /path/to/source  /path/to/dest

I want to call the script as a command with -l option.

myscript.sh -l "/media/t/my external hdd/backup/old one"

How should I write the part of the script which assigns to old_dest?

Tim
  • 101,790

2 Answers2

2

Since you're using bash, and since bash supports arrays, I would suggest making old_dest an array, instead:

#! /bin/bash

old_dest=()
while getopts ":l:t" opt; do
    case $opt in
        l)
            old_dest+=('--link-dest' "$OPTARG")
            ;;
    esac
done

printf 'Element: %s\n' rsync -a --delete "${old_dest[@]}" /path/to/source  /path/to/dest

I instrumented the call to rsync to instead be a call to printf to show the command and each argument on a separate line. When executed:

./myscript.sh -l "/media/t/my external hdd/backup/old one"

The result is:

Element: rsync
Element: -a
Element: --delete
Element: --link-dest
Element: /media/t/my external hdd/backup/old one
Element: /path/to/source
Element: /path/to/dest
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
  • Thanks. Manpage of rsync says --link-dest=DIR, so I think I made a mistake in my post. I am not sure how you manage to run rsync with --link-dest DIR. – Tim May 16 '18 at 01:18
  • Your reply indeed works, if it were --link-dest DIR. I have figured out solution for --link-dest=DIR. Thank you. – Tim May 16 '18 at 01:25
  • I didn’t run anything except printf, since I don’t have your environment. I just copied the command you have. – Jeff Schaller May 16 '18 at 01:25
2

The reason your command doesn't work is because quote removal/word splitting is not applied recursively.

So "$old_dest" is expanded to the SINGLE argument that follows:

--link-dest "/media/t/my external hdd/backup/old one"

All that is ONE argument, because the variable was in double quotes. (And those double quotes are removed, but the double quotes that resulted from the expansion, i.e. were part of the variable value, are not removed.)

If you used $old_dest instead, you would get multiple arguments, but not the way you want. You'd get the following FIVE arguments:

--link-dest
"/media/t/my
external
hdd/backup/old
one"

This is because you would get variable expansion followed by word splitting, as covered in the manual. You would not get variable expansion followed by interpretation of the quotes in the variable. And the quotes would not be removed, so the final argument would be the four characters: one".

The right way to do it, as covered in another answer, is to use Bash arrays.


On a separate note, you might enjoy how Tcl handles quotes and word splitting. It's probably more intuitive, although I'm so practiced with shell word splitting I have to think more about Tcl than shell.

Wildcard
  • 36,499
  • Thanks. In ls: invalid option -- ' ' of the output of my first example, why does it mention ' '? – Tim May 18 '18 at 00:26
  • @Tim, that almost warrants its own question, but here's a brief answer: by convention for most tools, options are either one hyphen and one letter or two hyphens and a human readable name. Multiple one-letter options can be combined by placing all such letters after the same hyphen; e.g. ls -laht is the same as ls -l -a -h -t. There is no -<space> option, though—and your first example has a literal space character amongst the characters all in the same argument following the single hyphen. – Wildcard May 18 '18 at 03:19