4

Can I also move or copy directory hierarchy of a file when I mv or cp the file?

For example, if I mv or cp dir1/dir2/file elsewhere, it will only mv or cp file. What if I want to also mv or cp dir1/dir2? Rather than mkdir -p dir1/dir2, can it be done with one command?

Tim
  • 101,790
  • Possible duplicate: http://unix.stackexchange.com/questions/83593/copy-specific-file-type-keeping-the-folder-structure - Server Fault duplicate : http://serverfault.com/questions/180853/how-to-copy-file-preserving-directory-path-in-linux – John WH Smith Oct 09 '14 at 13:00

1 Answers1

10

You can use cp's --parents switch:

$ mkdir -p step1/step2/step3
$ touch step1/step2/step3/file
$ mkdir copy

$ cp --parents step1/step2/step3/file copy
$ ls copy/step1/step2/step3/file
copy/step1/step2/step3/file

mv, however, does not have a --parents switch, but you could do something like:

$ find step1/step2/step3 -name "file" -exec cp --parents {} copy/ \; -delete

Which will:

  • Find the file.
  • Copy it (along with its parents) to the destination.
  • Delete the original.

You could create a function for this:

mvparents()
{
    [[ $# -ne 2 ]] && echo "2 arguments needed." && return
    [[ ! -r "$1" ]] && echo "$1 is not readable." && return
    [[ ! -d "$2" ]] && mkdir -p "$2"

    find $(dirname $1) -name "$(basename $1)" -exec cp --parents {} $2/ \; -delete
}

$ mvparents step1/step2/step3/file copy/

This might require a few adjustments if you try to move several files though. Here is an example (working in bash, but no guarantees for other shells) :

mvparents()
{
    [[ $# -lt 2 ]] && echo "Usage: $0 [source] <source> ... [destination]." && return

    # Get the destination directory.
    for dest; do true; done
    [[ ! -d "$dest" ]] && mkdir -p "$dest"

    # Copy the arguments and remove the destination.
    parameters=( "$@" )
    unset parameters[${#parameters[@]}-1]

    # For each source file: find, copy, delete.
    for source in "${parameters[@]}"; do
        if [ -r "$source" ]; then
            find $(dirname $source) -name "$(basename $source)" -exec cp --parents {} $dest/ \; -delete
        else
            echo "$0: $source is not readable."
        fi
    done
}

Well... that's a little longer than I expected but it should do the job

Edit: As Sebastian Piech pointed out in a comment, relying on cp to do mv's job is quite a performance killer, since mv does not originally need to copy the file, just edit its metadata. You might want to replace the above loop with:

for source in "${parameters[@]}"; do
    if [ -r "$source" ]; then
        [[ ! -d "$dest/$(dirname $source)" ]] && mkdir -p "$dest/$(dirname $source)"
        find $(dirname $source) -name "$(basename $source)" -exec mv {} "$dest/$(dirname $source)" \; -delete
    else
        echo "$0: $source is not readable."
    fi
done

However, since this thing relies a lot on the value of $PWD, I would ask you to be very careful should you use it.

John WH Smith
  • 15,880
  • There is no switch for mv, but you can find alternative solutions. I edited my answer. – John WH Smith Oct 09 '14 at 13:17
  • 1
    It's ok if you don't need great mv 'power' feature: mv command used to move a file/dir within the same partition doesn't touch files themselves but only changes directory content description. That makes mv very fast as it doesn't copy files content. – Sebastian Piech Oct 09 '14 at 14:27