Bash wildcard not expanding
Solution 1
A wildcard always expands to existing names.
Your command mkdir *
fails because the names that *
expands to already exists.
Your command mkdir *.d
"fails" because the *.d
does not match any existing names. The pattern is therefore left unexpanded by default1 and a directory called *.d
is created. You may remove this with rmdir '*.d'
.
To create a directory for each regular file in the current directory, so that the new directories have the same name as the files, but with a .d
suffix:
for name in ./*; do
if [ -f "$name" ]; then
# this is a regular file (or a symlink to one), create directory
mkdir "$name.d"
fi
done
or, for people that like "one-liners",
for n in ./*; do [ -f "$n" ] && mkdir "$n.d"; done
In bash
, you could also do
names=( ./* )
mkdir "${names[@]/%/.d}"
but this makes no checks for whether the things that the glob expands to are regular files or something else.
The initial ./
in the commands above are to protect against filenames that contain an initial dash (-
) in their filenames. The dash and the characters following it would otherwise be interpreted as options to mkdir
.
1 Some shells have a nullglob
shell option that causes non-matched shell wildcards to be expanded to an empty string. In bash
this is enabled using shopt -s nullglob
.
Solution 2
*.d
expands to the files whose name end in .d
.
With zsh
, you could do:
files=(*)
mkdir -- $^files.d
Or with an anonymous function:
() { mkdir -- $^argv.d; } *
Or adding the suffix via the e
glob qualifier:
mkdir -- *(e{REPLY+=.d})
Or the :s
history modifier applied to globs (with histsubstpattern
for %
to mean end):
set -o histsubstpattern
mkdir -- *(:s/%/.d)
You may also want not to do that for files that are already directories or symlinks to directories, by adding a ^-/
glob qualifier:
files=(*(^-/))
mkdir -- $^files.d
(note that zsh
doesn't have that misfeature of other Bourne-like shells where patterns that don't match are passed as-is, so mkdir *.d
would not create a *.d
directory if there was no file matching *.d
in the current directory, it would abort the command with an error instead)
Solution 3
Instead of for foo in bar; do baz; done
solution I would propose:
find . -maxdepth 1 -type f -print0|xargs -0 -I file mkdir file.d
Gooberpatrol66
Updated on September 18, 2022Comments
-
Gooberpatrol66 almost 2 years
I'm trying to make a directory for each file in a directory.
mkdir *
returns File exists. So I try
mkdir *.d
and it makes a directory called "*.d". How do I force the wildcard to expand?
-
user1934428 about 6 yearsConsider setting the failglob option in your .bashrc file. This would have warned you when you did the
mkdir *.d
.
-
-
Danielle about 6 yearsIt is bad idea to use
for name in ./*
. Instead I would go withfind . -depth 1 -and -type f -exec mkdir '{}.d' \;
which would work even if you have non-files in current directory. -
Kusalananda about 6 years@Hauleth In what way is it a bad idea? I'm testing each pathname before creating the directories, just like
find
would do. Youfind
command would probably use-maxdepth 1
rather than-depth 1
(-depth
does not take an argument) if-maxdepth
is supported, and-and
is not needed (and would make it non-portable). For a portablefind
variant:find . -type f -exec mkdir {}.d ';' -o ! -name . -type d -prune
, but that would also create directories for hidden files. -
Kusalananda about 6 years@Hauleth ... (and it would still depend on whether the
find
implementation expands{}.d
to the pathname concatenated with.d
, which it isn't required to do). -
Kusalananda about 6 yearsIf you'd like to avoid creating hidden directories (corresponding to hidden files), also use
! -name '.*'
. -
done about 6 yearsTo select only directories (in the bash solution) use
names=( ./*/ )
. -
Kusalananda about 6 years@Isaac Yes, but in this case we would ideally only want to select regular files. That's why I also mentioned that caveat.