86

I'm trying to escape the space char for a path in Bash, but neither using a backslash or quotes works.

.sh script:

ROOT="/home/hogar/Documents/files/"
FILE=${ROOT}"bdd.encrypted"
DESTINATION="/home/hogar/Ubuntu\ One/folder"

mv ${FILE} ${DESTINATION}

After execute the script (./file) this is the result:

mv: target 'One/folder' is not a directory

Why does the mv command split the string, and how do I stop this from happening?

Volker Siegel
  • 17,283
Lucio
  • 1,307

3 Answers3

100

You are expanding the DESTINATION variable, if you did echo this is what you would get:

echo ${DESTINATION}
/home/hogar/Ubuntu\ One/folder

But mv doesn't understand this:

mv ${FILE} ${DESTINATION}                                                
mv: cannot move '/home/hogar/Documents/files/bdd.encrypted' to '/home/hogar/Ubuntu\\ One/folder': No such file or directory

(for some reason my mv is more verbose)

To prevent this you should use quotes instead:

mv "${FILE}" "${DESTINATION}"

If you don't need expansion (since you are already expanding before) just using "$..." should suffice:

mv "$FILE" "$DESTINATION"
Volker Siegel
  • 17,283
Braiam
  • 35,991
  • 12
    Furthermore, if you double-quote everything consistently, you don't need the backslash before the space. – 200_success Jan 10 '14 at 05:12
  • 3
    ${variable} and $variable are equivalent in all ways, unless immediately followed by a character that is valid in a variable name. – Kusalananda Aug 07 '18 at 06:29
29

Your step to prepare the variables is "close enough" and will work, but it does show a weakness in understanding (which is why you posted!)

The assignment to DESTINATION needs to be only one of these two:

DESTINATION="/home/hogar/Ubuntu One/folder"

or

DESTINATION=/home/hogar/Ubuntu\ One/folder

The double-quotes turns on quoting (a form which has some magic) and the backslash is capable of quoting a single character following it. Personally I think the form using double-quotes is preferable because visually it's nicer.

By the way, for similar reasons I would do the FILE assignment like:

FILE="${ROOT}bdd.encrypted"

which shows the fairly rare occasion to use ${NAME} instead of simply $NAME - to separate the variable name from any letter following. In fact, if you didn't have a trailing / on the definition of ROOT, I'd be writing

FILE="$ROOT/bdd.encrypted"

which looks even better. It will also give you occasional benefits that I won't go into; let's just say in my experience, trailing slashes tend to be more unwelcome than welcome, and they are definitely not needed. (It does have consequences when it comes to symlinks but that's definitely too much detail on this already large answer).

The crux of your issue is actually taking place at the mv command, which should be written as

mv "$FILE" "$DESTINATION"

because these days, you never know when a variable representing a path will have spaces in it. Once again, note the avoidance of braces for a simple variable expansion.

The reason you got a problem is because of the process the shell uses to construct command lines. If you read the shell manual carefully, it basically says that variables (and a few other things) get expanded THEN it goes looking for spaces. Of course there's some more complexity in the process, but this is the gist of the problem for you.

One further point that's related and well worth knowing about: the distinction between $*, $@, "$*" and "$@". So I'll talk about it!

If you have a shell script that may be called as:

foo -x "attached is the software" "please read the accompanying manual"

then foo will see -x as $1, and the two strings as $2 and $3 (which you should access as "$2" and "$3" for obvious reasons). If, however, you want to pass this entire set of parameters to another script, the following will suffer horrible splitting on all the spaces:

bar $*

and the following (which was the only way in version 0.X of the very original Bourn shell) will cause all the arguments to be passed as a single string (also WRONG)

bar "$*"

so the following syntax was added as a very special magical case:

bar "$@"

which deliberately quotes the individual members of $* as complete quoted strings but keeps them separate. Everyone is a winner now.

It's kinda entertaining to experiment with other effects of $@ when not used as "$@" but you'll quickly find out it's not actually that entertaining... it is merely a special case that solves a major problem.

All the decent modern shells which support arrays also use @ in a special case:

${arr[*]}
"${arr[*]}"
"${arr[@]}"

where the first one will suffer splitting on all spaces, leading to an unpredictable number of separate words, the second will yield all the array members in one string, and the third will very nicely offer each array member as an individual unbroken quoted string.

Enjoy!

  • I'm new to bash after years with tcsh. I have a shell variable that I need to use often to refer to a folder with spaces in its name. I don't want to wrap every reference to it in double quotes, which would slow down my typing. I want the quotes to be INSIDE the variable value and to cause the variable value to be treated as a string. How do I get that? Example: od='"OneDrive - MyCompany"' ; cd $od This fails. But after expanding the variable I thought I'd have a quoted string: cd "OneDrive - MyCompany". If I type that directly it works. Why doesn't it as the result of expansion? – All The Rage Nov 09 '20 at 17:02
14

Yes you escaped space when you assign the value to $DESTINATION,

But when you use it with mv command, you didn't

mv ${FILE} ${DESTINATION}

Use this instead:

mv "${FILE}" "${DESTINATION}"
Braiam
  • 35,991
daisy
  • 54,555