linuxbashcp

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

  • 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