4

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?

  • Consider setting the failglob option in your .bashrc file. This would have warned you when you did the mkdir *.d. – user1934428 Jun 20 '18 at 8:00
19

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.

  • It is bad idea to use for name in ./*. Instead I would go with find . -depth 1 -and -type f -exec mkdir '{}.d' \; which would work even if you have non-files in current directory. – Hauleth Jun 19 '18 at 13:43
  • 1
    @Hauleth In what way is it a bad idea? I'm testing each pathname before creating the directories, just like find would do. You find 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 portable find variant: find . -type f -exec mkdir {}.d ';' -o ! -name . -type d -prune, but that would also create directories for hidden files. – Kusalananda Jun 19 '18 at 14:08
  • @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 Jun 19 '18 at 14:16
  • To select only directories (in the bash solution) use names=( ./*/ ). – Isaac Jun 19 '18 at 19:02
  • @Isaac Yes, but in this case we would ideally only want to select regular files. That's why I also mentioned that caveat. – Kusalananda Jun 19 '18 at 19:03
2

*.d expands to the files whose name end in .d.

With zsh, you could do:

files=(*)
mkdir -- $^files.d

Or:

mkdir -- *(e{REPLY+=.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

Or:

mkdir -- *(e{REPLY+=.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)

2

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

  • If you'd like to avoid creating hidden directories (corresponding to hidden files), also use ! -name '.*'. – Kusalananda Jun 19 '18 at 14:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.