2

I've been trying to create the directory "a" inside all the subdirectories of "example".

What I tried is the following:

mkdir example/*/a

But it doesn't take the '*' as a wildcard, returning mkdir: can not create «example/*/a» directory: It doesn't exist file or directory

Note that I have too much subdirectories in example, so I can't do it by creating a list.

I don't want to do it with for loops, because I'm learning bash and I'm not allowed to use loops. Probably, there is no way to avoid a for loop, in that case, please tell me :).

Thank you.

dipese
  • 91
  • 5
  • 1
    _they are not fast enough_ : How many million of directories are you trying to create, so that speed is an issue? I think a loop is the fastest way. – user1934428 Mar 01 '22 at 11:51
  • 1
    "they are not fast enough". If you have performance concerns, you probably shouldn't be using a shell at all. – William Pursell Mar 01 '22 at 11:52
  • 1
    @WilliamPursell and user1934428: I'm a computer engineering student and I'm currently learning bash. So, I've been taught that loops in bash, as it is dynamically typed, may be avoided if there is another way of doing it. So I have a task of moving a lot of files to other directories that I have to create without using loops. I understand your point, but I'm just learning, I'm not into business stuff. Anyway, thank you for your contributions :) – dipese Mar 01 '22 at 12:03
  • The performance issue in this case comes not from looping but from running `mkdir` individually for each directory to be created. On my [Cygwin](https://en.wikipedia.org/wiki/Cygwin) system, for example, a single `mkdir` takes around a 10th of a second to run. Even 100 directories is a big deal. Solutions that invoke `mkdir` just once, or a minimum number of times, are *very much quicker*. Using a loop to create input for `xargs -0 mkdir` would probably give acceptable performance. Don't worry about the performance of loops until you have found, and analyzed the causes of, real problems. – pjh Mar 01 '22 at 14:02

4 Answers4

2

Probably, there is no way to avoid a for loop

You can use find:

find example -type d -not -name a -exec mkdir -p {}/a \;
anubhava
  • 761,203
  • 64
  • 569
  • 643
2

Try:

dirs=( example/*/ )
mkdir -v "${dirs[@]/%/a}"
  • dirs=( example/*/ ) creates an array of all the subdirectories of example ( (example/dir1/ example/dir2/ ...) ).
  • "${dirs[@]/%/a}" expands to a list containing all the elements of dirs with an a appended to each of them ( (example/dir1/a example/dir2/a ...) ).

If you have a very large number of subdirectories the mkdir in the code above may fail with an "Argument list too long" error. See Argument list too long error for rm, cp, mv commands.

One way to avoid the problem is to pass a NUL-terminated list of the directories to be created to xargs -0:

dirs=( example/*/ )
printf '%s\0' "${dirs[@]/%/a}" | xargs -0 mkdir -v

That will, if necessary, run multiple mkdir commands, each with an argument list that does not exceed the limit.

However, if the number of directories is that large you may run into performance problems due to Bash (unnecessarily, but unavoidably) sorting the list produced by example/*/). In that case it would be better to use find. One way to do it is:

find example -maxdepth 1 -mindepth 1 -type d -printf '%p/a\0' | xargs -0 mkdir -v

find has built-in support for xargs-like behaviour with -exec ... +. However, it's a bit fiddly to use in this case. The best I can come up with in a hurry is:

find example -maxdepth 1 -mindepth 1 -type d    \
    -exec bash -c 'mkdir -v "${@/%//a}"' bash {} +

Since it adds /a to all of the arguments passed to bash by find before running mkdir on them, it may encounter an "Argument list too long" error anyway. I'll leave it here because it illustrates a sometimes-useful technique, but I recommend against using it.

pjh
  • 6,388
  • 2
  • 16
  • 17
1

Just:

for i in example/*/; do
    mkdir "$i"/a
done
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
0

I think you can only avoid a loop if you already know the subdirectories under example, e.g.:

mkdir example/{subdir1,subdir2,subdir3}/a

Otherwise, use a for loop to the rescue:

for d in example/*/; do mkdir "${d}a"; done

Note: add a -v to mkdir if you want it to show you the directories that get created.

Costi Ciudatu
  • 37,042
  • 7
  • 56
  • 92