10

I have a directory foo with subdirectories. I wish to create the same subdirectories names in another directory without copying their content. How do I do this?

Is there a way to get ls output as a brace expansion list?

0

5 Answers 5

24

Using rsync:

rsync -a --include='*/' --exclude='*' /some/path/dir/ dir

This would recreate the directory structure of /some/path/dir as dir in the current directory, without copying any files.

Any directory encountered in the source path would be created at the target due to the inclusion pattern, but anything else would be excluded. As a side effect of using -a (--archive), you'll get the same timestamps on all subdirectories in the target as in the source. This also works for creating local directory structures from remote directories (and vice versa).

0
13

Try this,

cd /source/dir/path
find . -type d -exec mkdir -p -- /destination/directory/{} \;
  • . -type d To list directories in the current path recursively.
  • mkdir -p -- /destination/directory/{} create directory at destination.

This relies on a find that supports expanding {} in the middle of an argument word.

4
  • 6
    This may break if any file or folder name in the top level begins with a dash. It also omits directories that begin with a dot. Finally, be aware that not every implementation of find supports the interpolation of {} into another string. Commented Sep 23, 2019 at 11:26
  • 1
    And it is not very scalable - it will take plenty of time if there are millions of directories. Commented Sep 23, 2019 at 11:35
  • 6
    If there are many things in /source/dir/path, then this might fail with an "argument list too long" error when the shell tries to call find with the expansion of *. Better to just use . there. Also, most find implementations allows {} to be used even when concatenated with another string, but it's not universal.
    – Kusalananda
    Commented Sep 23, 2019 at 18:29
  • 1
    You can use . Instead of * in this case. (Using xarg for performance and security would probably require an external script for the path concatenation)
    – eckes
    Commented Sep 24, 2019 at 10:11
8

You can use find to traverse the source structure and call mkdir for each directory it meets.

This example, using find, will copy your directory structure from foo to /tmp/another/

( cd foo && find -type d -exec sh -c 'for d do mkdir -p "/tmp/another/$d"; done' sh {} + )

The exec loop builds up the set of directories underneath foo, which is then passed to the mkdir. If you don't have a version of find that understands + you can use \; at the cost of efficiency. Substitute mkdir with echo mkdir to see what would happen without actually doing it.

2
  • exactly with GNU find (4.7.0-git) this doesn't seem to work: find: In ‘-exec ... {} +’ the ‘{}’ must appear by itself, but you specified ‘/tmp/another/{}’ (it does work with -exec ... \;, though)
    – ilkkachu
    Commented Sep 25, 2019 at 8:29
  • Ilkkachu I'm sure I tested /path/to/{} but I now can't find any version where it works so I've adapted the solution. Thank you Commented Sep 26, 2019 at 13:35
4

With the bash shell, you could ask it for the expansion of every directory with the globstar option:

shopt -s globstar

and then copy the directories with a loop:

for dir in **/
do
  mkdir -p /path/to/dest/"$dir"
done

... or if you thought they'd all fit within one call to mkdir:

set -- **/
mkdir -- "${@/#//path/to/dest/}"

That's bash array syntax that says: "take every element of the $@ array and replace the beginning of each of them with /path/to/dest/.

I'm not aware of a way to get ls to output directly as a brace expansion list. If you tried to massage the output of the **/ expansion into a brace expansion, you would need to be careful to:

  • escape any commas in the output
  • escape any { or ${ sequence
  • ensure the resulting string didn't exceed the available command-line argument space

I wouldn't recommend it.

2

The question is a cross-site duplicate of https://superuser.com/questions/1389580/copy-directory-structure-only-at-year-end

This kind of task is a classic use case for mtree:

$ mkdir new-tree
$ mtree -cp old-tree | mtree -tdUp new-tree
.:      modification time (Tue Sep 24 14:27:07 2019, Tue Sep 24 16:34:57 2019, modified)
./bar missing (created)
./bar/bar2 missing (created)
./bar/bar2/bar3 missing (created)
./bar/bar2/bar3/bar4 missing (created)
./foo missing (created)
./foo/foo2 missing (created)
./foo/foo2/foo3 missing (created)

The above creates all the directories under new-tree that were present under old-tree. mtree does not set timestamps on newly-created directories, however, so the resulting tree looks like this:

$ find old-tree new-tree -ls
 20147  1 drwx--x---   4 jim   jim   5 Sep 24 14:27 old-tree
 20048  1 drwx--x---   3 jim   jim   4 Sep 24 14:27 old-tree/foo
 20363  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/foo/file
 20073  1 drwx--x---   3 jim   jim   4 Sep 24 14:27 old-tree/foo/foo2
 20074  1 drwx--x---   2 jim   jim   3 Sep 24 14:27 old-tree/foo/foo2/foo3
 20365  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/foo/foo2/foo3/file
 20364  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/foo/foo2/file
 20051  1 drwx--x---   3 jim   jim   4 Sep 24 14:27 old-tree/bar
 20077  1 drwx--x---   3 jim   jim   4 Sep 24 14:27 old-tree/bar/bar2
 20368  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/bar/bar2/file
 20078  1 drwx--x---   3 jim   jim   4 Sep 24 14:27 old-tree/bar/bar2/bar3
 20369  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/bar/bar2/bar3/file
 20079  1 drwx--x---   2 jim   jim   3 Sep 24 14:27 old-tree/bar/bar2/bar3/bar4
 20370  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/bar/bar2/bar3/bar4/file
 20366  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/bar/file
 20362  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/file
134489  1 drwx--x---   4 jim   jim   4 Sep 24 16:34 new-tree
134490  1 drwx--x---   3 jim   jim   3 Sep 24 16:34 new-tree/bar
134491  1 drwx--x---   3 jim   jim   3 Sep 24 16:34 new-tree/bar/bar2
134492  1 drwx--x---   3 jim   jim   3 Sep 24 16:34 new-tree/bar/bar2/bar3
134493  1 drwx--x---   2 jim   jim   2 Sep 24 16:34 new-tree/bar/bar2/bar3/bar4
134494  1 drwx--x---   3 jim   jim   3 Sep 24 16:34 new-tree/foo
134495  1 drwx--x---   3 jim   jim   3 Sep 24 16:34 new-tree/foo/foo2
134496  1 drwx--x---   2 jim   jim   2 Sep 24 16:34 new-tree/foo/foo2/foo3

If you prefer to have the new-tree timestamps match those in old-tree, simply run mtree again. Since the directories already exist, mtree will modify the timestamps to match the source specification:

$ mtree -cp old-tree | mtree -tdUp new-tree
.:      modification time (Tue Sep 24 14:27:07 2019, Tue Sep 24 16:34:57 2019, modified)
bar:    modification time (Tue Sep 24 14:27:07 2019, Tue Sep 24 16:34:57 2019, modified)
bar/bar2: 
        modification time (Tue Sep 24 14:27:07 2019, Tue Sep 24 16:34:57 2019, modified)
bar/bar2/bar3: 
        modification time (Tue Sep 24 14:27:07 2019, Tue Sep 24 16:34:57 2019, modified)
bar/bar2/bar3/bar4: 
        modification time (Tue Sep 24 14:27:07 2019, Tue Sep 24 16:34:57 2019, modified)
foo:    modification time (Tue Sep 24 14:27:07 2019, Tue Sep 24 16:34:57 2019, modified)
foo/foo2: 
        modification time (Tue Sep 24 14:27:07 2019, Tue Sep 24 16:34:57 2019, modified)
foo/foo2/foo3: 
        modification time (Tue Sep 24 14:27:07 2019, Tue Sep 24 16:34:57 2019, modified)
$ find old-tree new-tree -ls
 20147  1 drwx--x---   4 jim   jim   5 Sep 24 14:27 old-tree
 20048  1 drwx--x---   3 jim   jim   4 Sep 24 14:27 old-tree/foo
 20363  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/foo/file
 20073  1 drwx--x---   3 jim   jim   4 Sep 24 14:27 old-tree/foo/foo2
 20074  1 drwx--x---   2 jim   jim   3 Sep 24 14:27 old-tree/foo/foo2/foo3
 20365  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/foo/foo2/foo3/file
 20364  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/foo/foo2/file
 20051  1 drwx--x---   3 jim   jim   4 Sep 24 14:27 old-tree/bar
 20077  1 drwx--x---   3 jim   jim   4 Sep 24 14:27 old-tree/bar/bar2
 20368  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/bar/bar2/file
 20078  1 drwx--x---   3 jim   jim   4 Sep 24 14:27 old-tree/bar/bar2/bar3
 20369  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/bar/bar2/bar3/file
 20079  1 drwx--x---   2 jim   jim   3 Sep 24 14:27 old-tree/bar/bar2/bar3/bar4
 20370  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/bar/bar2/bar3/bar4/file
 20366  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/bar/file
 20362  1 -rw-------   1 jim   jim   0 Sep 24 14:27 old-tree/file
134489  1 drwx--x---   4 jim   jim   4 Sep 24 14:27 new-tree
134490  1 drwx--x---   3 jim   jim   3 Sep 24 14:27 new-tree/bar
134491  1 drwx--x---   3 jim   jim   3 Sep 24 14:27 new-tree/bar/bar2
134492  1 drwx--x---   3 jim   jim   3 Sep 24 14:27 new-tree/bar/bar2/bar3
134493  1 drwx--x---   2 jim   jim   2 Sep 24 14:27 new-tree/bar/bar2/bar3/bar4
134494  1 drwx--x---   3 jim   jim   3 Sep 24 14:27 new-tree/foo
134495  1 drwx--x---   3 jim   jim   3 Sep 24 14:27 new-tree/foo/foo2
134496  1 drwx--x---   2 jim   jim   2 Sep 24 14:27 new-tree/foo/foo2/foo3

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .