Differences between revisions 14 and 35 (spanning 21 versions)
Revision 14 as of 2009-05-06 19:34:30
Size: 2149
Editor: GreyCat
Comment: add the word "hierarchy" to the question so I can find it
Revision 35 as of 2023-09-22 06:29:48
Size: 3137
Comment:
Deletions are marked like this. Additions are marked like this.
Line 3: Line 3:
Line 4: Line 5:

{{{
 cd "$srcdir"
 find . -type d -print | cpio -pdumv "$dstdir"
{{{#!highlight bash
cd -- "$srcdir" &&
find . -type d -print | cpio -dumpv -- "$dstdir"
Line 11: Line 11:

{{{
 find . -type d -print | pax -rwdv "$dstdir"
{{{#!highlight bash
cd -- "$srcdir" &&
find . -type d -print | pax -rwdv -- "$dstdir"
Line 17: Line 17:
{{{#!highlight bash
cd -- "$srcdir" &&
find . -type d -print | tar c --files-from - --no-recursion |
  tar x --directory "$dstdir"
}}}
This creates a list of directory names with `find`, non-recursively adds just the directories to an archive, and pipes it to a second `tar` instance to extract it at the target location. As you can see, `tar` is the least suited to this task, but people just adore it, so it has to be included here to appease the `tar` fanboy crowd. (Note: you can't even do this at all with a typical Unix `tar`. Also note: there is no such thing as "standard tar", as both `tar` and `cpio` were intentionally omitted from POSIX in favor of `pax`.)
Line 18: Line 24:
{{{
 cd "$srcdir"
 find . -type d -print | tar c --files-from - --no-recursion |
   tar x --directory "$dstdir"
All the solutions above will fail if directory names contain newline characters. On many modern BSD/GNU systems, at least, they can be trivially modified to cope with that, by using `find -print0` and one of `pax -0` or `cpio -0` or `tar --null` (check your system documentation to see which of these commands you have, and which extensions are available). If you really don't have access to those options, you can probably, at least, use `! -path $'*\n*' -type d -print`, or better `-name $'*\n*' -prune -o -type d -print` (instead of `-type d -print`) to ignore directories that contain newline characters in their path, making sure `find` is run in the `C`/`POSIX` locale to also exclude file paths containing newline characters as well as sequence of bytes not forming valid characters in the user's locale.

with find
{{{#!highlight bash
export dstdir
mkdir -p -- "$dstdir" &&
cd -- "$srcdir" &&
find . -type d -exec sh -c \
  'cd -- "$dstdir" && mkdir -- "$@"' sh {} +
Line 24: Line 35:
This creates a list of directory names with `find`, non-recursively adds just the directories to an archive, and pipes it to a second `tar` instance to extract it at the target location. As you can see, `tar` is the least suited to this task, but people just adore it, so it has to be included here to appease the `tar` fanboy crowd. (Note: you can't even do this at all with a typical Unix `tar`. Also note: there is no such thing as "standard tar", as both `tar` and `cpio` were intentionally omitted from POSIX in favor of `pax`.)

All three of the solutions above will fail if directory names contain newline characters. On many modern BSD/GNU systems, at least the first two can be trivially modified to cope with that, by using `find -print0` and either `pax -0` or `cpio -0`.

If you want to create stub files instead of full-sized files, with GNU [[UsingFind|find(1)]], the following is likely to be the simplest solution. The `find` command recreates the regular files using "dummy" files (empty files with the same timestamps):

{{{
 cd "$srcdir"
 # insert pax/cpio command here, to make the directories
 find . -type f -exec touch -r {} "$destination"/{} \;
or with bash 4's `globstar`
{{{#!highlight bash
shopt -s globstar nullglob &&
cd -- "$srcdir" && dirs=(**/) && (( ${#dirs[@]} )) &&
cd -- "$dstdir" && mkdir -- "${dirs[@]}"
Line 36: Line 42:
Be aware, though, that according to POSIX, the behaviour of `find` is unspecified when `{}` is not standing alone in an argument. Because of this, the following solution is more portable (and probably faster...) than the previous: (though beware that will also copy symlinks to directories as directories; older versions of bash would also traverse symlinks when crawling the directory tree).
Line 38: Line 44:
{{{
 dstdir=whatever; export dstdir
 find . -type f -exec sh -c 'for i; do touch -r "$i" "$dstdir"/"$i"; done' _ {} +
or with zsh's recursive globbing and glob qualifiers:
{{{#!highlight bash
export srcdir dstdir
zsh -ec '
cd -- "$srcdir"
dirs=(**/*(/D))
cd -- "$dstdir"
mkdir -- $dirs'
Line 43: Line 54:
If your `find` can't handle `-exec +` then you can use `\;` instead of `+` at the end of the command. See UsingFind for explanations. If you want to create stub files instead of full-sized files, the following is likely to be the simplest solution. The `find` command recreates the regular files using "dummy" files (empty files with the same timestamps):
{{{#!highlight bash
cd -- "$srcdir" &&
DSTDIR=$dstdir find . -type f -exec sh -c \
  'for i do touch -r "$i" -- "$DSTDIR/$i"; done' sh {} +
}}}

If your `find` can't handle `-exec +` then you can use `\;` instead of `+` at the end of the command. See [[UsingFind]] for explanations.

----
CategoryShell

How can I recreate a directory hierarchy structure, without the files?

With the cpio program:

   1 cd -- "$srcdir" &&
   2 find . -type d -print | cpio -dumpv -- "$dstdir"

or with the pax program:

   1 cd -- "$srcdir" &&
   2 find . -type d -print | pax -rwdv -- "$dstdir"

or with GNU tar, and more verbose syntax:

   1 cd -- "$srcdir" &&
   2 find . -type d -print | tar c --files-from - --no-recursion |
   3   tar x --directory "$dstdir"

This creates a list of directory names with find, non-recursively adds just the directories to an archive, and pipes it to a second tar instance to extract it at the target location. As you can see, tar is the least suited to this task, but people just adore it, so it has to be included here to appease the tar fanboy crowd. (Note: you can't even do this at all with a typical Unix tar. Also note: there is no such thing as "standard tar", as both tar and cpio were intentionally omitted from POSIX in favor of pax.)

All the solutions above will fail if directory names contain newline characters. On many modern BSD/GNU systems, at least, they can be trivially modified to cope with that, by using find -print0 and one of pax -0 or cpio -0 or tar --null (check your system documentation to see which of these commands you have, and which extensions are available). If you really don't have access to those options, you can probably, at least, use ! -path $'*\n*' -type d -print, or better -name $'*\n*' -prune -o -type d -print (instead of -type d -print) to ignore directories that contain newline characters in their path, making sure find is run in the C/POSIX locale to also exclude file paths containing newline characters as well as sequence of bytes not forming valid characters in the user's locale.

with find

   1 export dstdir
   2 mkdir -p -- "$dstdir" &&
   3 cd -- "$srcdir" &&
   4 find . -type d -exec sh -c \
   5   'cd -- "$dstdir" && mkdir -- "$@"' sh {} +

or with bash 4's globstar

   1 shopt -s globstar nullglob &&
   2 cd -- "$srcdir" && dirs=(**/) && (( ${#dirs[@]} )) &&
   3 cd -- "$dstdir" && mkdir -- "${dirs[@]}"

(though beware that will also copy symlinks to directories as directories; older versions of bash would also traverse symlinks when crawling the directory tree).

or with zsh's recursive globbing and glob qualifiers:

   1 export srcdir dstdir
   2 zsh -ec '
   3 cd -- "$srcdir"
   4 dirs=(**/*(/D))
   5 cd -- "$dstdir"
   6 mkdir -- $dirs'

If you want to create stub files instead of full-sized files, the following is likely to be the simplest solution. The find command recreates the regular files using "dummy" files (empty files with the same timestamps):

   1 cd -- "$srcdir" &&
   2 DSTDIR=$dstdir find . -type f -exec sh -c \
   3   'for i do touch -r "$i" -- "$DSTDIR/$i"; done' sh {} +

If your find can't handle -exec + then you can use \; instead of + at the end of the command. See UsingFind for explanations.


CategoryShell

BashFAQ/010 (last edited 2023-09-22 06:29:48 by StephaneChazelas)