Using a shell pattern such as {abc,def}xyz I can use it to create files (or directories) that don't yet exist:

$ ls
$ touch {abc,def}xyz
$ mkdir 123{a,b,c}
$ ls
123a  123b  123c  abcxyz  defxyz

What is puzzling me is how to create a subdirectory within each of a set of folders matching a pattern. For example, now that I have 123a, 123b, and 123c can I create a subdirectory in each of them without having to resort to listing them explicitly?

$ mkdir {12*}/rsnapshot         # Fails because 12* isn't a list
$ mkdir 12*/rnapshot            # Fails because 12*/rsnapshot doesn't exist
$ mkdir 123{a,b,c}/rsnapshot    # Succeeds but more complex than I'd like

The last example works, but requires me to list some part of every directory name in the {...} subclause. If I have a large number of directories this is a non-starter.

I've also got this line to work for simple names that don't contain spaces, but it's neither obvious nor elegant for a generalised solution:

echo -n 12* '' | xargs -r -d ' ' -I {} echo mkdir {}/rsnapshot

Is there a pattern template in bash that will allow me to create files or subdirectories within a large set of similarly named subdirectories without a loop?

  • Perhaps using zsh and its array variables might help. Sep 30, 2017 at 13:22
  • to deal with spaces, I'd use for d in 123*; do mkdir "$d"/rsnapshot; done
    – Sundeep
    Sep 30, 2017 at 13:51
  • @Basile I just knew there'd be a zsh solution waiting for me (!) but today I'm looking for something in bash Sep 30, 2017 at 16:34
  • What is the problem with using a loop? Surely a clear, explicit loop is readable and maintainable. Sep 30, 2017 at 18:28
  • @glennjackman I can reference a (potentially very) large number of files or directories with a pattern such as 123*. I'd like to leverage that capability to avoid having to type out a potentially large list of similar names. A loop would work but bash does that implicitly when I use * and other glob characters Sep 30, 2017 at 20:36

8 Answers 8


Just re-call previous command arguments with !$

mkdir 123{a,b,c}
mkdir !$/rsnapshot

You could use find to create sub-directories within pattern specified directory name if you are not going with loops.

find . -maxdepth 1 -type d -name "123*" -exec mkdir '{}/rsnapshot' \;
  • 2
    I don't think that was OP's intention... more like those folders exist and now sub-directories need to be created... if it was, can use mkdir -p 123{a,b,c}/rsnapshot
    – Sundeep
    Sep 30, 2017 at 14:07
  • Correct, then my knowledge is helping me just using find or a loop to find the existing directory with matched pattern then do sub-directory creation Sep 30, 2017 at 14:19
  • I was looking for a generalised solution. Repeating the last arg could be fine in a situations but it doesn't solve the problem I thought I'd described Sep 30, 2017 at 16:33
dirs=( 123* )
set -- "${dirs[@]/%//deep/and deeper}"
mkdir -p "$@"

I don't think this needs an explanation...

  • Others might prefer the explanation but I do like this for its brevity Oct 1, 2017 at 8:04
  • This will match any 123 directory/file in the pwd. Not only the limited set of the ones crated. Is that a valid answer? @roaima
    – user232326
    Oct 1, 2017 at 19:11
  • @Arrow very much so. The question is the last sentence; the rest is example data illustrating the issue Oct 1, 2017 at 20:01

I don't see any chance to get a solution which qualifies as "less complex than 123{a,b,c}/rsnapshot". But maybe our views how complex that is differ a lot...

But I can offer something "shell-like". Unfortunately it seems impossible (with bash) to configure the character which separates the elements resulting from pathname expansion thus it gets a bit more, eh, complex:

:> dirs=(12*/.) # the dot makes it match directories only
:> echo ${dirs[*]}
123a/. 123b/. 123c/.
:> IFS=,
:> eval echo mkdir -p "{${dirs[*]}}/rsnapshot"
mkdir -p 123a/./rsnapshot 123b/./rsnapshot 123c/./rsnapshot

Of course, this works only if none of the pathname expansion matches contains a comma. This may be prevented with $GLOBIGNORE.

printf and xargs

I stand corrected:

printf "%s/rsnapshot\0" 12*/. | xargs -0 echo mkdir -p
  • The {a,b,c} works for the trivial case but doesn't extend well to tens of directories (or more) Sep 30, 2017 at 16:26
  • The printf solution with its implied looping could work really well. Sep 30, 2017 at 16:31
  • The eval approach is outright dangerous if your directory names aren't constrained. Consider if mkdir 12'$(rm -rf ~)' had been run prior. Sep 30, 2017 at 19:40
  • @CharlesDuffy I assume that the comment apply to my (old) answer with eval. Thanks, but That answer has been re-written anyway. The comment seems that is no longer needed.
    – user232326
    Oct 2, 2017 at 13:51

With zsh, you can do:

mkdir $^l/snapshot

The $^array enables some sort of brace-like expansions for arrays like is done implicitly in rc/es or fish.


mkdir 12*(e:REPLY+=/snapshot:)

With rc/es

mkdir $l/snapshot

With fish:

set l 12*
mkdir $l/snapshot
  • Of course, 12* will match 124d 12ee or 12f, none of which seem that were intended to be matched in the question.
    – user232326
    Oct 1, 2017 at 19:22
  • @Arrow it is an example set of data. The question I'm trying to get answered is about any possible shortcut to generating a set of matching directory structures where the set of existing directories is too large to be enumerated easily but that the full expanded pathname does not (yet) exist. Oct 1, 2017 at 19:59

As it seems (from other allowed answers) that any 123 dir is valid:

mkdir "${dirs[@]/%/rsnapshot}"

Since you are creating the directories, you may as well extend that to:

 mkdir -p 123{a..c}{,/rsnapshot}

That will create both the directories you wanted as well as the sub-directories you ask for.

But your questions doesn't specify: Only the directories previously crated.

That makes the selection of directories fuzzy at best.

  1. If there is no limit as to which directories should be extended with a subdirectory, we might as well select all of them:

    dirs=( */ )
    mkdir "${dirs[@]/%/rsnapshot}"
  2. A more limited selection could be done with the names of the directories. If the names start with some characters (123):

    dirs=( 123*/ )
    mkdir "${dirs[@]/%/rsnapshot}"
  3. If the directories are created just before the selection: finding newer directories might be the solution:

    sleep 5          # newest directory created is older than 5 seconds.
    mkdir 123{a..d}  # create newer directories.
    find . -type d -newermt '4 seconds ago'
  4. In two steps, shell history could be included:

    $ mkdir -p 123{a..c}
    $ mkdir -p !${,/rsnapshot}
  5. Eval is a distinct solution but usually quite dangerous.

  • I want to avoid explicitly enumerating the list of directories in the mkdir {pattern}/subdirectory Sep 30, 2017 at 19:27
  • I don't see how that could be avoided except for history expansion of two commands in sequence, which is not that useful in an script.
    – user232326
    Sep 30, 2017 at 19:38
  • A longer list of possible solutions @roaima
    – user232326
    Oct 2, 2017 at 13:46

You can do this by generating an array of currently-existing directories, then expanding the array with a substitution that appends "rsnapshot" to each element:

$ dirs=(123*/)   # Make an array with the currently-existing subdirs
$ echo "${dirs[@]}"    # Check the contents before proceeding
123a/ 123b/ 123c/
$ echo mkdir "${dirs[@]/%/rsnapshot}"    # echo to show what it'll do without doing it
mkdir 123a/rsnapshot 123b/rsnapshot 123c/rsnapshot

(To use it, remove the echos as needed.)

There's a bit of a subtlety here: I included a trailing "/" in the pattern I used to create the array; this means that it'll only match directories, not any files that happen to match the pattern. It also means that "/" is explicitly included in each element of the array (as you can see when I echo it), so I only need to append "rsnapshot", not "/rsnapshot".

The final substitution, "${dirs[@]/%/rsnapshot}", can be read as: take each element of dirs (dirs[@]), and replace (/) the end-of-string (%) with (/) the string "rsnapshot".

  • @roaima: It's very similar to that answer, except that I don't recommend using set to replace the current argument list; just use the array directly. Also, including the trailing "/" in the pattern will prevent it from trying to create subdirectories under things that aren't directories. I thought about making a comment on that answer to suggest these changes, but decided my suggestion was different enough to justify a different answer. Oct 2, 2017 at 1:13
  • (That, and I thought the array trickery deserved some explanation.) Oct 2, 2017 at 1:41

The curly brace pattern is for expanding repetitions; as you point out, it's not matched against existing files and so it's not really a "filename pattern". To create subdirectories in existing directories, I think it's simpler (and more general) to do it the old-fashioned way:

mkdir 123{a,b,c}
for dir in 123*
    mkdir $dir/rsnapshot

That wasn't so hard, was it? Or if you're a fan of xargs you could use that instead:

echo 123* | xargs -I {} mkdir {}/rsnapshot

In my opinion the best solution is the one that uses common tools whose use you can remember when you just want to get the job done and move on.

You must log in to answer this question.

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