Skip to content
Advertisement

Better bash script to create directory structure

I want to create folders with plain text with a structure like:

Folder
| Subfolder
| | Deeper-subfolder
| Subfolder

I thought about using a base command like mkdir and found that post : Bash script that creates a directory structure, but it still needs to write structure like :

Folder
Folder/subfolder
Folder/subfolder/deeper-subfolder
Folder/subfolder

So I would need to change the regex used in :

sed '/^$/d;s/ ///g' struct.txt | xargs mkdir -p

What bash command could I use to do this?

Advertisement

Answer

Using pure sed:

#!/bin/sed -f

x
G
: loop
s:([^/]*)/*(.*n)([^|]*)|s:231/:
t loop

s/.*n//
s:/*$::
h

For this we have to use the hold space (an auxiliary buffer) to store the previous dir path. Then we can remove directories from the hold space and replace them into the respective |s. A more detailed explanation follows:

This script is executed for every line. First thing we do is to exchange (“x”) the hold space and the pattern space. Initially, the hold space will be empty, but after the first line the hold space will contain the previous directory path. By exchanging them, we load the previous directory and save the current new directory into hold space.

Next, we append the new directory name to the previous directory path (but both will be separated by newlines). The command used for this is “G”.

Now we have to loop for every | character we find. To do this is the tricky part, so bear with me. Suppose we have the following in the current pattern space

Folder/subdir
| | deeper-subdir
  1. Firstly, the ([^/]*) matches everything before the first slash and captures it (ie. stores it in a “variable”, named 1 because it is the first capture group).
  2. Then we skip slashes: /*
  3. Capture everything up to the newline (inclusive) and store into capture 2: (.*n)
  4. Capture everything before the first | character and store into capture 3: ([^|]*)
  5. Skip the | character followed by a space character: |s

Now, the captures look like this:

  1. Folder
  2. subdirn
  3. 3.

All we have to do is regenerate the lines reording the captures and adding another slash so they become:

subdir
Folder/| deeper-subdir

After that, we have a conditional branch command “t” that only banches back to the “loop” label if the previous “s” command succeeded. That means that when the string is ready (no more |s), we can exit the loop and continue with the code.

Following the example, the next iteration contains the captures:

  1. subdir
  2. n
  3. Folder/

After the loop, the first substitute command removes the first line, which contains the path that the new directory hasn’t entered, (ie. the new directory is “shallower” than the previous one).

The last substitute command removes any slashes from the end of the line and the last command is a hold command (“h”) which copies the current generated path to the hold space, so we can repeat everything for the next line.

Hope this helps =)

Advertisement