Search code examples
bashnesteddirectory-structuremkdirsubdirectory

Creating Hierarchical Directory Tree From Compact String Representation


I know I can create a series of nested directories via a rather verbose series of mkdir -p with the -p flag applied to suppress warnings that the directory already exists. This gets very verbose with individual calls, i.e.:

mkdir -p hello
mkdir -p hello/i_am_a
mkdir -p hello/i_am_a/boy
mkdir -p hello/i_am_a/girl
mkdir -p hello/i_am_a/alien
mkdir -p hello/i_am_a/dog
mkdir -p hello/my_name_is
mkdir -p hello/my_name_is/jim
mkdir -p hello/my_name_is/bob
mkdir -p hello/my_name_is/secret

I would like to instead build the tree or at least part of it in a single call with a nested pattern consisting of a list-opening character (i.e. {), list delimiter character (i.e. ,)., and list-closing character (i.e. }).

For each level starting with the top, a directory of that name is created if it does not exist already.

To continue my silly example above with real words, I would use the input string:

'hello{i_am_a{boy,girl,alien,dog},my_name_is{jim,bob,secret}}'

...which should create the following...

hello
hello/i_am_a
hello/i_am_a/boy
hello/i_am_a/girl
hello/i_am_a/alien
hello/i_am_a/dog
hello/my_name_is
hello/my_name_is/jim
hello/my_name_is/bob
hello/my_name_is/secret

For convenience, let's say that if it impacts the solution's length, we can assume there's no more than n layers of nesting in the resultant tree (e.g. n=3 in my example above, for instance).

Let us further assume for convenience that the three special structure characters (in the above example, for instance {, ,, and }) will never occur in the nested strings, i.e. they will never show up in the resulting file paths.

It seems a Perl or Python embedded script might do the trick, but perhaps there's a simpler solution using built-in BASH functionality?

My goal is a concise solution that cuts down on the verbosity of my data post-processing scripts, which often involve the creation of hierarchical directory trees populated at deeper levels with intermediates that I save for reuse and with finished results in the directories towards the top of the tree.


Solution

  • Try this:

    mkdir -p hello/{i_am_a/{boy,girl,alien,dog},my_name_is/{jim,bob,secret}}
    

    seems to do the job:

    find *
    hello
    hello/i_am_a
    hello/i_am_a/dog
    hello/i_am_a/alien
    hello/i_am_a/boy
    hello/i_am_a/girl
    hello/my_name_is
    hello/my_name_is/bob
    hello/my_name_is/secret
    hello/my_name_is/jim
    

    ... further?

    This will create 30 files (named file_0a to file_2j) in each of this directories:

    touch hello/{i_am_a/{boy,girl,alien,dog},my_name_is/{jim,bob,secret}}/file_{0..2}{a..j}
    

    ( For populating this 210 files, you could replace touch by date | tee , for sample. )

    find * -ls | sed -ne '1,7{p;d};:;N;$!b;${s/^.*\(\(\n[^\n]*\)\{5\}\)\n*$/...\1/p;}'
    1703938   4 drwxr-xr-x   4 user user  4096 avr  3 22:23 hello
    1703939   4 drwxr-xr-x   6 user user  4096 avr  3 22:23 hello/i_am_a
    1703943   4 drwxr-xr-x   2 user user  4096 avr  3 22:23 hello/i_am_a/dog
    1704066   0 -rw-r--r--   1 user user     0 avr  3 22:23 hello/i_am_a/dog/file_2i
    1704057   0 -rw-r--r--   1 user user     0 avr  3 22:23 hello/i_am_a/dog/file_1j
    1704061   0 -rw-r--r--   1 user user     0 avr  3 22:23 hello/i_am_a/dog/file_2d
    1704052   0 -rw-r--r--   1 user user     0 avr  3 22:23 hello/i_am_a/dog/file_1e
    ...
    1704076   0 -rw-r--r--   1 user user     0 avr  3 22:23 hello/my_name_is/jim/file_0i
    1704097   0 -rw-r--r--   1 user user     0 avr  3 22:23 hello/my_name_is/jim/file_2j
    1704074   0 -rw-r--r--   1 user user     0 avr  3 22:23 hello/my_name_is/jim/file_0g
    1704073   0 -rw-r--r--   1 user user     0 avr  3 22:23 hello/my_name_is/jim/file_0f
    1704081   0 -rw-r--r--   1 user user     0 avr  3 22:23 hello/my_name_is/jim/file_1d
    

    ... And as Jonathan Leffler suggest, have a look at man -Pless\ +/Brace.Expansion bash

    eval and printf for different content into many different files

    Instead of touch you could: eval <(printf 'echo $RANDOM >%s\n' ...):

    mkdir -p hello/{i_am_a/{boy,girl,alien,dog},my_name_is/{jim,bob,secret}}
    eval <(printf 'echo "$RANDOM" >%s\n' \
      hello/{i_am_a/{boy,girl,alien,dog},my_name_is/{jim,bob,secret}}/file_{0..2}{a..j}
    )