'(solved) bash script: how to cope with the different cp behaviors?

Among the tons of cp questions I have not found anything about this difference in behaviour (tested on Ubuntu 18.04). Sorry for the lost post, but the setting is a bit complex.

Case 1: This is the expected behaviour

Given the following folder

 source                     
    file1.cpp
    file2.cpp

after running this script

 #!/bin/bash
 cp -r source/ target/   

I do get this result

 source                     # same as above, plus a copy in "target"
    file1.cpp
    file2.cpp
 target  
    file1.cpp
    file2.cpp

Case 2: Using the same script, source folder exists and is empty in the target folder

Here there is one additional, empty folder source
file1.cpp file2.cpp target source

and run the same script

 #!/bin/bash
 cp -r source/ target/     

which gives me a different, undesired result

 source                     # same as above, plus a copy in "target"
    file1.cpp
    file2.cpp
 target  
    source
       file1.cpp
       file2.cpp

Normal Solution for Case1 and Case2

 cp -r source/  target/       # works only for Case 1
 cp -r source/* target/       # works only for Case 2

Used in the wrong case, one will cause an error, the other yield the wrong result which can be very confusing. This means for each copy action I must check if the target folder exists and use a different command. That is very cumbersome but I am not aware of a more simple solution.

Unresolved situation for Case2

However, the problem I have is this one: When I use variables for the source and target my script looks like this

 #!/bin/bash
 SOURCE="source"
 TARGET="target"

 if [ -d "$TARGET" ]; then
   cp -r $SOURCE $TARGET     
 else
   cp -r $SOURCE/* $TARGET     # note "$SOURCE/*" would fail.
 fi

and I have a $SOURCE path with spaces.

SOURCE="source code"

Since I can not use quotations for the source variable, this causes two 'directory not found errors'.

How can I solve this for Case2?

EDIT

To clarify the problem a bit more. This

SOURCE="source" 
cp -r "$SOURCE/*" $TARGET

fails with the error "cannot stat source/: No such file or directory". I think that means that bash can not replace the / with the file list and cp gets this as a file literal. A file or folder with the name "source/*" obviously does not exist. But maybe I am thinking too simple and what bash does is different.



Solution 1:[1]

Although your main issue concerns the cp command there is also something I would like to say about your try. So lets get it step by step.

Issue 1: cp behaviour

The cp command behaves differently when the target folder contains a folder with the same name than the source folder. This behaviour is intended but can be avoided using the -T option according to man.

Here you can find an extended explanation of the -T option.

Thus, you can just execute:

cp -rT source/ target/

Issue 2: Paths containing spaces

In your try you mention issues when handling paths using spaces. Although with the previous solution you don't need a custom script, I want to highlight that variables with paths containing spaces require all accesses to be double-quoted. The variable content must be double-quoted, not the star (since you want the globstar to expand, not to be taken literally). Hence, your previous script would look like this:

#!/bin/bash

SOURCE=${1:-"source"}
TARGET=${2:-"target"}

if [ -d "$TARGET" ]; then
  cp -r "$SOURCE" "$TARGET"     
else
  cp -r "$SOURCE"/* "$TARGET"    # note "$SOURCE/*" would fail.
fi

Solution 2:[2]

Maybe us CP for individual files and wse rsync for directories ?

rsync -vazh /source /destination

(includes 'source' dir)

rsync -vazh /source/ /destination

(does not include 'source' dir)

Solution 3:[3]

#!/bin/bash
cp -r --copy-contents source/ target/

--copy-contents means copy only the content of source

for understand better that happend in your case test this script:

#!/bin/bash
pwd

you can do some like that if this pwd write different places:

#!/bin/bash
cd SOURCE_PARENT
cp -r --copy-contents source/ target/

if you have spaces in the target or source name have this in mind cp [OPTION]... SOURCE... DIRECTORY

target name <= "target realTarget"
cp -r source target realTarget

cp -r source target realTarget believe that you want copy source & target in realTarget it don't understand that "target realTarget" is a full name if you need copy it you have to use the double quotation marks in the comandad

cp -r source "target realTarget"

cp -r source "target realTarget" now "target realTarget" is taken as folder name

with source happend the same use the double quotation marks and you will solve the problem

->with your extra: (escape the quotes using \)

SOURCE="\"source realsource\"" 
cp -rT --copy-contents $SOURCE $TARGET

-T, --no-target-directory treat DEST as a normal file

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Mike Q
Solution 3