Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I'm trying to copy multiple files to a directory from within a shellscript. These files contain all sorts of "ugly" characters, such as whitespaces, brackets and what not else. However, I'm stuck when it comes to escaping these as bash and cp seem to handle these very strangely.

Here is the scenario:
When issuing this command from within my shell it works like a charm:
cp /somedir/a\ file.png /somedir/another\ file.png /someotherdir/

However, when reading the files to copy from a string, it suddenly becomes weird:
var="/somedir/a\ file.png /somedir/another\ file.png"
cp "$var" /someotherdir/

Results in cp: cannot stat 'a\\ file.png another\\ file.png': No such file or directory

I believe this is due to the fact that I give the variable as a string and cp believes it's one file, although it's multiple files. When issuing the same command without putting the variable in quotes (var="/somedir/a\ file.png /somedir/another\ file.png"; cp $var /someotherdir/) I get an even more weird error:
cp: cannot stat 'a\\ file.png another\\ file.png': No such file or directory
cp: cannot stat 'file.png': No such file or directory
cp: cannot stat 'another\\': No such file or directory
cp: cannot stat 'file.png': No such file or directory

It seems to completely ignore my escaping. What am I doing wrong?

EDIT:// It seems like Copy list of files has an answer with xargs, but I still wonder why bash is acting so weird here.

share|improve this question
add comment

3 Answers 3

up vote 2 down vote accepted

Doctor, it hurts when I do this…

Then don’t do that!

Seriously, why are you trying to assign a single variable a space-separated list of filenames that contain spaces?  That’s a recipe for disaster.

To answer your implied question: I agree that the error messages you show don’t make sense for the commands you say you used.

Since you haven’t said how you determine which files to copy, it’s hard to know what solution will work for you and what won’t.  Here’s a possible approach:

        ︙
f1="/somedir/a file.png"
        ︙
f2="/somedir/another file.png"
        ︙
cp "$f1" "$f2" "$f3" … /someotherdir

(You don’t need the / at the end of /someotherdir/, but it doesn’t hurt, either.)  This is constrained by the facts that you need to know in advance the maximum number of files you will need to copy, and you need some way of figuring out what f variable to assign each one to.

This is close to what you’re doing now:

var="/somedir/a\ file.png /somedir/another\ file.png …"
echo "$var" | while read f1 f2 f3 …
do
    cp "$f1" "$f2" "$f3" … /someotherdir
done

The read command will break the $var string apart at the plain spaces, and strip out the backslashes, leaving as just   (space).  But you still need to know in advance the maximum number of files you will need to copy.  (You need the while loop construct even though this loop will iterate only once.)

Perhaps this one is getting close:

filelist=/tmp/my_filelist.$$
        ︙
echo "/somedir/a file.png" >> "$filelist"
        ︙
echo "/somedir/another file.png" >> "$filelist"
        ︙
while read filename
do
    cp "$filename" /someotherdir
done < "$filelist"

Another variation is to use a variable array.  You can learn how to do that in the bash man page.

share|improve this answer
    
The number of files to copy is variable and unknown at program start. I've changed my code to seperate the list with new lines and now I'm parsing it with xargs. Works fine too –  MechMK1 Feb 26 '13 at 6:02
add comment

When bash parses a command line, it first interprets quotes, escapes, and such, and then replaces variables. If there are escapes, quotes, etc in the variable values, they never take effect because by the time they're included in the command, it's too late for them to have the intended effect.

Generally, the best way to handle this sort of problem is to store the file names in an array (one element per filename) rather than a string variable; then use "${array[@]}" to expand the array with each element being treated as a separate "word":

files=(/somedir/a\ file.png /somedir/another\ file.png)
cp "${files[@]}" /someotherdir/

Update: if you need to build the file list dynamically, that's easy with an array:

files=() # Start with an empty list
for newfile in somethingorother; do
    files+=("$newfile")
done
share|improve this answer
add comment

If you're using bash, you can double quote the name of the file to avoid using escape characters:

cp /old_location/This\ is\ an\ \[ugly\] \file.tmp /new_location/This\ is\ an\ \[ugly\] \file.tmp

use this instead when using #!/bin/bash in your shellscript:

cp "/old_location/This is an [ugly] file with extras $$$.tmp" "/new_location/Ugly file with extras $$$.tmp"

In your case, you are using the values inside a variable, but you're passing the variable wrong. If a variable has spaces, you should double quote it aswel:

var="/somedir/a\ file.png /somedir/another\ file.png"; cp "$var" /someotherdir/

If you don't double quote it, it will be like using several variables separed with spaces.

share|improve this answer
    
The problem is that that does not work with multiple files –  MechMK1 Feb 25 '13 at 16:44
    
I've updated the answer with your solution, use "$var" :P –  Peter Feb 25 '13 at 16:51
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.