Make many subdirectories at once
In bash, use something like these lines:
mkdir -p {mydir,hisdir,herdir}/doc
touch {mydir,hisdir,herdir}/doc/doc1.txt
The {...}
syntax is called "brace expansion", and unlike pathname expansion, where the filename must exist, the generated results don't need to match anything already there. And the -p
means create all nested components of the path as needed -- otherwise, you'll get an error as mkdir attempts to create the final "doc" dirs before the parents.
(Check out the examples in the bash man page; creating subdirectories like this is exactly the common use case.)
If mydir, hisdor, and herdir already exist and you don't want to retype them, Stéphane Chazelas's solution is probably the most clever, but unless you do it all of the time, clever isn't always best — I can never remember the bash array expansion stuff offhand, and I bet many junior sysadmins wouldn't recognize it. In that case, I think I'd recommend either a loop or find
, like this:
find . -maxdepth 1 -mindepth 1 -type d \
-execdir mkdir {}/doc \; -execdir touch {}/doc/doc1.txt \;
but, really, the simple loop has the virtue of being straightforward — and not much more typing!
With zsh
:
dirs=(*(/))
mkdir -- $^dirs/doc
touch -- $^dirs/doc/doc1.txt
(/)
is a globbing qualifier, /
means to select only directories.
$^array
(reminiscent of rc
's ^
operator) is to turn on a brace-like type of expansion on the array, so $^array/doc
is like {elt1,elt2,elt3}/doc
(where elt1
, elt2
, elt3
are the elements of the array).
One could also do:
mkdir -- *(/e:REPLY+=/doc:)
touch -- */doc(/e:REPLY+=/doc1.txt:)
Where e
is another globbing qualifier that executes some given code on the file to select.
With rc
/es
/akanga
:
dirs = */
mkdir -- $dirs^doc
touch -- $dirs^doc/doc1.txt
That's using the ^
operator which is like an enhanced concatenation operator.
rc
doesn't support globbing qualifiers (which is a zsh-only feature). */
expands to all the directories and symlinks to directories, with /
appended.
With tcsh
:
set dirs = */
mkdir -- $dirs:gs:/:/doc::q
touch -- $dirs:gs:/:/doc/doc1.txt::q
The :x
are history modifiers that can also be applied to variable expansions. :gs
is for global substitute. :q
quotes the words to avoid problems with some characters.
With zsh
or bash
:
dirs=(*/)
mkdir -- "${dirs[@]/%/doc}"
touch -- "${dirs[@]/%/doc/doc1.txt}"
${var/pattern/replace}
is the substitute operator in Korn-like shells. With ${array[@]/pattern/replace}
, it's applied to each element of the array. %
there means at the end.
Various considerations:
dirs=(*/)
includes directories and symlinks to directories (and there's no way to exclude symlinks other than using [ -L "$file" ]
in a loop), while dir=(*(/))
(zsh extension) only includes directories (dir=(*(-/))
to include symlinks to directories without adding the trailing slash).
They exclude hidden dirs. Each shell has specific option to include hidden files).
If the current directory is writable by others, you potentially have security problems. As one could create a symlink there to cause you to create dirs or files where you would not want to. Even with solutions that don't consider symlinks, there's still a race condition as one may be able to replace a directory with a symlink in between the dirs=(*/)
and the mkdir...
.