'Copy files to destination and create destination if does not exist

I am writing a bash script to copy ONE or MORE THAN ONE or ALL files to a destination, and create the destination if it does not exist.

I have already gone through this solution but it does not complete my requirements.

My code (Which does not work for idk what reasons):

# copy files and make a directory if does not exist
mkcp() {
    # last argument is destination
    dir="${@: -1}"
    # create directory if does not exist
    mkdir -p "$dir"
    # loop over all arguments
    for file in "$@"
    do
        # if file is last argument (directory) then break loop
        if [ "$file" == "$dir" ]; then
            break
        fi
        # else keep copying files
        cp -R "$file" "$dir"
    done
}

I want all these commands to be working:

# copies "text.txt" to "testdir" (testdir may or may not exist so it must be created first)
$ mkcp test.txt ~/desktop/testdir

# copies "test1.txt" and "test2.txt" to "testdir" (conditions are same)
$ mkcp test1.txt test2.txt ~/desktop/testdir

# copies "all files" to "testdir" (conditions are same)
$ mkcp * ~/desktop/testdir

If there's any other solution that can complete my requirements, I am okay with it too.

Note: The mkcp function is stored in .zshrc.



Solution 1:[1]

You assume that the last argument must be a directory, and if no such directory exists, it should be created. In zsh, I would do it like this:

mkcp() {
  local destdir=$@[-1]
  if [[ -f $destdir ]]
  then
    echo Missing destination directory 2>&1 
    return 1
  else
    mkdir -p $destdir
    if [[ -d $destdir ]]
    then
      cp "$@"
    else
      echo Can not create $destdir
      return 1
    fi
  fi
}

The problem is not the copying (the plain cp command can do it and no loop is needed), so the focus here is on error checking.

Note that with my approach, switches can be passed implicitly to cp, for instance

mkcp -r foo bar baz

copies recursively the subdirectories too, and

mkcp -rv foo bar baz

is in addition printing the names of the files which are copied.

Solution 2:[2]

I added this bash answer before I realized this is a zsh question. Not deleting it in hopes others find it useful.


You can take a sublist of the positional arguments using the ${var:offset:length} expansion. See Shell Parameter Expansion in the manual.

Perhaps this:

mkcp() {
    local dir="${@: -1}"

    mkdir -p "$dir"

    for file in "${@:0:$#}"     # all but last
    do
        cp -vR "$file" "$dir"
    done
}

In fact, it can be simpler, assuming you have GNU cp with the -t option:

mkcp() {
    local dir="${@: -1}"
    mkdir -p "$dir"
    cp -t "$dir" -vR "${@:0:$#}"
}

I added cp's -v option for extra verbosity, so you can see what files are being copied.

Solution 3:[3]

This is what the main changes I suggest should look like:

mkcp() {
    # last argument is destination
    dir="${@: -1}"
    # create directory if does not exist
    mkdir -p "$dir"
    # loop over all arguments
    for file in "$@"
    do
        # if file is last argument (directory) then break loop
        if [ "$file" == "$dir" ]; then
            break
        fi
        # else keep copying files
        cp -R "$file" "$dir"
    done
}

I'm not clear how you'd get the error message mkcp:9: = not found; to debug that, I'd need to see your modified code. OTOH, I'm not convinced that debugging that is going to be particularly constructive.

Solution 4:[4]

The error is on this command

# copies "all files" to "testdir" (conditions are same)
$ mkcp * ~/desktop/testdir

You're passing a string "*" as command to a custom function, so when you send *mkcp * you're actually saying "cp -R * /path" where * is just a string, not 'all'.

You can try to do this way:

mkcp() {
    # last argument is destination
    dir="${@: -1}"
    # create directory if does not exist
    mkdir -p "$dir"
    
    # loop over all arguments
    for file in "$@"
    do
        if [[ "${file}" == "*" ]]; then
            LIST_FILE=$(ls $dir)
            for i in $LIST_FILE
            do
               cp $i $dir
            done
            break
        fi
        # if file is last argument (directory) then break loop
        if [ file == "$dir" ]; then
            break
        fi
        # else keep copying files
        cp -R "$file" "$dir"
    done
}

If you need to 'debug' what the script is executing, try to run the script as:

bash -x mkcp * ~/desktop/testdir

The -x will how each step of the script.

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 user1934428
Solution 2 glenn jackman
Solution 3 Jonathan Leffler
Solution 4 DharmanBot