I created a 'cron' subdirectory in /var/log/
. It is: /var/log/cron
Within /var/log/cron
I've created numerous sub-directories to store my log files for cron jobs. I am wondering if there is a way to output mkdir
to match the directory structure my automated jobs use.
For example these sub-directory structure exists within
| ronshome
|-- personal_growth
| |-- building_your_relationship
| |-- fun_things_that_make_life_worth_living
| |-- hope_is_found
| |-- life_lessons
| |-- thought_of_the_day
| |-- what_makes_a_good_friend
| |-- what_my_friends_mean_to_me
| `-- who_am_i
My desired output is:
mkdir /var/log/cron/ronshome
mkdir /var/log/cron/ronshome/personal_growth
mkdir /var/log/cron/ronshome/personal_growth/building_your_relationship
mkdir /var/log/cron/ronshome/personal_growth/fun_things_that_make_life_worth_living
etc.
Essentially if I need to do a server restore I would like to be able to use this to recreate the log directories.
5 Answers 5
You could just make a tar containing all the directories (not the files they contain). Something like
# if in bash, you need to enable recursive globs:
shopt -s globstar
# the trailing '/' is important! it only matches directories, not files.
tar --no-recursion -cvzf directories.tar.gz /var/log/cron/**/
then you can just extract your directories.tar.gz file to recreate the directory tree.
You could just as well use the same globbing to create a list of things to mkdir
to put in a shell script, but that will put the burden of proper quoting of directory names on you, for no good reason: a tar file is specifically a file designed to allow you to recreate a directory tree from specification.
(I don't think --no-recursion
is POSIX, but it's in GNU tar and bsdtar, and honestly, that's enough for any reasonably expected machine this is needed on :) )
-
4+1. a tar file will also handle ownership and permissions of each directory, which might be useful if any of the subdirs aren't owned by root or don't have the default perms (probably 755 or 775, depending on root's umask setting).cas– cas2025年06月08日 02:31:47 +00:00Commented Jun 8 at 2:31
-
2
tar
is not a POSIX command.pax
is the POSIX command to createtar
archives.Stéphane Chazelas– Stéphane Chazelas2025年06月09日 15:05:42 +00:00Commented Jun 9 at 15:05 -
@StéphaneChazelas
tar
is not a POSIX command It used to be. GNU tar embraced, extended, and extinguishedtar
as a standardAndrew Henle– Andrew Henle2025年06月10日 21:21:19 +00:00Commented Jun 10 at 21:21
Using mtree
(available in the netbsd-mtree
package on Debian), we can save a "specification" of a directory hierarchy in a text file:
$ mtree -p /var/log/cron -cd >var-log-cron.mtree
This prints the specification (-c
) of the /var/log/cron
directory hierarchy, omitting everything but the directories (-d
) and saves it to a text file.
You can then compare another hierarchy with the stored specification using
$ mtree -f var-log-cron.mtree -p /var/log/cron -d
This would report extra or missing directories as well as metadata changes (timestamps, ownerships, filetypes).
To update the hierarchy according to the specification in the text file, use
$ mtree -f var-log-cron.mtree -p /var/log/cron -du
(Note the added -u
.) This would recreate the hierarchy according to the stored specification.
-
Arch doesn't have a maintained package for this (aur/nmtree appears to be abandoned), but it includes libarchive's bsdtar by default, where
bsdtar -cf foo.mtree --format=mtree /var/log/cron
would do the same – probably combined with Marcus's--no-recursion
to exclude non-directories. (Then the same bsdtar can be used to "extract" a mtree.)grawity– grawity2025年06月07日 22:03:01 +00:00Commented Jun 7 at 22:03
for a quick solution, something along the lines of
find -type d -print0 | gzip -9 > dirtree.gz
# ^ direc- ^ end
# tories each
# only find in
# a zero-
# byte
gzip -d dirtree | xargs -0 mkdir -p
if you don't need any information about ownership and acccess mode.
Based on Marcus's answer:
shopt -s globstar
printf 'mkdir %q\n' /var/log/cron/**/ > recreate.sh
will generate a sh script that will re-create the directories when run.
Bash's printf %q
uses backslash escaping for its arguments. Newer Bash versions support an alternative ${var@Q}
that uses quotation marks for somewhat more pleasant looking output (except when the string itself contains a single-quote, which will still be correctly processed but will look ugly); this is done at variable expansion time so it needs a temporary array variable:
shopt -s globstar
dirs=(/var/log/cron/**/)
printf 'mkdir %s\n' "${dirs[@]@Q}" > recreate.sh
Both options produce sh&bash-compatible quoting.
The external GNU Coreutils /bin/printf %q
also uses single quotes (see also).
A third option (this time the output is Bash-specific) is to use declare -p
to output a variable in a form that can be directly read back:
shopt -s globstar
dirs=(/var/log/cron/**/)
declare -p dirs > recreate.bash
echo 'mkdir -p "${dirs[@]}"' >> recreate.bash
-
oh nice! didn't know about
%q
, nor about${...@Q}
. Learnt something!Marcus Müller– Marcus Müller2025年06月07日 21:59:19 +00:00Commented Jun 7 at 21:59 -
When I try the 4 line command I am getting an error message:
3: shopt: not found
I put these 4 lines into a .sh file and added#!/bin/bash
as line 1. Can you spot what I've done wrong?Ron Piggott– Ron Piggott2025年06月08日 04:36:01 +00:00Commented Jun 8 at 4:36 -
@RonPiggott Are you running it via sh or bash? The #!/bin/bash is correct, if you then chmod +x and run it as "./generate.sh", but e.g. if you're doing "sh generate.sh" then that negates any #! header. You specifically need bash for these commands. And more specifically I believe shopt was added in bash 1.14 whereas
globstar
needs bash 4.0 or later (2009).grawity– grawity2025年06月08日 07:03:10 +00:00Commented Jun 8 at 7:03 -
If you're running bash I'd suggest doing these commands one by one directly from interactive shell. They don't do much that would make it necessary to have them in separate script.grawity– grawity2025年06月08日 07:06:59 +00:00Commented Jun 8 at 7:06
-
You should indicate which of Marcus's answers you're referencing - e.g. using a link.Toby Speight– Toby Speight2025年06月10日 15:39:45 +00:00Commented Jun 10 at 15:39
rsync
You can use rsync
first to include all directories and then exclude all files [1].
rsync -av -f"+ */" -f"- *" /path/to/src /path/to/dest/
or with older syntax [2]
rsync -a --include='*/' --exclude='*' source/ destination/
It may also work on different machines and not just locally adding user@host:
e.g.
rsync -a --include='*/' --exclude='*' user1@src_host:source/ user2@dest_host:destination/
For more information, it is always close to us man rsync
.
mkdir -p
can save you the trouble of recreating all the parents; you only need to give it the deepest directories. (This is not part of most good ways of solving this specific problem, just a side note.)