10

I have a file in a directory as /a/b/c/xxxxx.sql

I have another directory, /a/b/d, having no further subdirectories.

How should I copy xxxxx.sql into /a/b/d, to have the new file with directory as /a/b/d/c/xxxxx.sql, in a single command?

Kusalananda
  • 333,661
  • 4
    why? What does it matter if you use a single command or the obvious two-command combination? The directory creation and file creation isn't atomic anyway, so in an unlucky case, another process could see the new directory, take a listing of it and find find nothing there. Worse, writing the data to the new file is also not atomic, so similarly another process could see the file there with partial data. If you want the dir and file to appear atomically, it's more a question of setting them up first and moving them into place after that. – ilkkachu May 11 '23 at 18:20
  • 3
    If there's no real technical reason to do it in a special way, the obvious way is likely more familiar to the eventual future reader of the script. – ilkkachu May 11 '23 at 18:21

5 Answers5

19

With the install command:

install -D -m644 /a/b/c/xxxxx.sql /a/b/d/c/xxxxx.sql

From man install:

install - copy files and set attributes

You can chose the Unix rights by modifying 644 by what you want, default is 755 as that command is primarily intended for software deployment. The umask is ignored.

The command will transparently create all the directory structure if not exists with -D switch:

-D

Create all leading components of DEST except the last, or all components of --target-directory, then copy SOURCE to DEST

The directories that are created in the process will always have rwxr-xr-x (755) permissions (though possibly affected by setgid bits and default ACLs) regardless of the umask. Directories that were already there won't have their permissions modified.

AdminBee
  • 22,803
12

Using GNU cp, do the copy from the starting point of the path you want to see reflected in the new directory and use the --parents option:

cd /a/b &&
  cp --parents c/foo d

Example:

% cp --parents c/foo d --verbose
c -> d/c
'c/foo' -> 'd/c/foo'

To make it a single command:

{ cd /a/b && cp --parents c/foo d; } # compound command
env -C /a/b cp --parents c/foo d  # simple command assuming BSD/GNU env

With -a, cp will copy all attributes from the source including file type, permissions, ACLs, extended attributes, sparseness and ownership if permitted by the system, both for the c directory and the foo file.

muru
  • 72,889
8

You can use rsync relative option.

rsync --relative /a/b/./c/xxxxx.sql /a/b/d/

Note that . separates where --relative path should start.

To move the whole directory (c), don't forget to add --recursive flag to copy these files recursively.

breversa
  • 135
DaG
  • 339
  • rsync -R gives the error sh:/a/b/d/ :Is a directory – Siddharth Somani May 10 '23 at 20:36
  • I am running this code using system() in R. Is the error because of this ? – Siddharth Somani May 10 '23 at 21:07
  • 3
    @SiddharthSomani, that error message indicates it's by sh not rsync, and rsync doesn't run sh. system() functions of many languages do invoke sh, but that exact command line wouldn't trigger a EISDIR error. Possibly you have rsync -R /a/b/./c/xxxxx.sql > /a/b/d/ instead of rsync -R /a/b/./c/xxxxx.sql /a/b/d/. – Stéphane Chazelas May 11 '23 at 07:14
5

In addition to rsync, tar will do what you need:

tar cf - -C /a/b c/xxxxxx.sql | tar xpf - -C /a/b/d

An example:

$ mkdir -p /a/b/c /a/b/d
$ touch /a/b/c/xxxxxx.sql
$ tar cf - -C /a/b c/xxxxxx.sql | tar xpf - -C /a/b/d
$ find /a/b
/a/b
/a/b/c
/a/b/c/xxxxxx.sql
/a/b/d
/a/b/d/c
/a/b/d/c/xxxxxx.sql
Jim L.
  • 7,997
  • 1
  • 13
  • 27
4

You can roll your own command too. Here's a function implementation but you could equally drop a variant of this into an executable script somewhere along your $PATH

mdcopy() {
    mkdir -p -- "$2" &&
        cp -p -- "$1" "$2"
}

Usage

mdcopy /a/b/c/xxxxx.sql /a/b/d/c

An advantage of this approach is that you can have the utility to exactly what you want. This implementation requires a directory as its second argument but you could equally adapt it to expect a file pathname as the second argument. It's whatever works best for you.

Chris Davies
  • 116,213
  • 16
  • 160
  • 287