4

In Linux the following two commands work as expected:

mkdir -m 555 new_directory
mkdir -p a/b/c

But the following does not work as expected:

mkdir -m 555 -p a/b/c

the 3 directories are created but only the latest recieves the 555 permission. The a and b directories have the default permissions.

So how accomplish the goal described in the title? Is it possible?

BTW - I selected 555 how a random case, it fails with 666 and 777 too

12
  • 1
    It's not possible with mkdir
    – Bravo
    Commented Jan 6, 2022 at 22:57
  • 1
    interesting, it doesn't even obey the umask setting for the intermediates, but forces umask to 022. I do wonder if that counts as a bug though...
    – ilkkachu
    Commented Jan 6, 2022 at 23:31
  • 2
    @ManuelJordan uff! Important info that comes too late! EDIT your question to include this! Commented Jan 6, 2022 at 23:46
  • 1
    Related: What's the difference between mkdir -p and install -d? Commented Jan 7, 2022 at 0:11
  • 1
    @ManuelJordan thanks for making the edit :) Commented Jan 7, 2022 at 0:16

4 Answers 4

3

If you expressly list the directories, parent first, you can achieve your stated aim of creating the directories in one command:

mkdir -m 555 -p a a/b a/b/c

With shells with support for csh-style brace expansion such as bash you can simplify this a little at the expense of readability:

mkdir -m 555 -p a{,/b{,/c}}

Notice, however, that for permissions 555 both commands will fail if it actually needs to create any of the parent directories: such directories are created with permissions that do not allow writing, and therefore next level directories cannot be created.

Finally, a bash shell script that will also give you the functionality to create the multiple directories in one command as requested, by wrapping the complexity in a function. This one will attempt to apply the permissions to newly created directories from the bottom up, so it will be possible to end up with directories that have no write permission:

mkdirs()
{
    local dirs=() modes=() dir old

    # Grab arguments
    [[ "$1" == '-m' ]] && modes=('-m' "$2") && shift 2
    dir=$1

    # Identify missing directories
    while [[ "$dir" != "$old" ]]
    do
        [[ ! -d "$dir" ]] && dirs+=("$dir")
        old="$dir"
        dir="${dir%/*}"
    done

    # Create necessary directories and maybe fix up permissions
    for dir in "${dirs[@]}"
    do
        mkdir -p "${modes[@]}" "$dir" || return 1
        [[ -n "${modes[1]}" ]] && chmod "${modes[1]}" "$dir"
    done
}

Example

mkdirs -m 555 a/b/c

ls -ld a a/b a/b/c
dr-xr-xr-x+ 1 roaima roaima 0 Jan  7 10:01 a
dr-xr-xr-x+ 1 roaima roaima 0 Jan  7 10:01 a/b
dr-xr-xr-x+ 1 roaima roaima 0 Jan  7 10:01 a/b/c

As always, this function can be put standalone into an executable script that's somewhere in your $PATH:

#!/bin/bash
mkdirs()
{
    ...as above...
}

mkdirs "$@"
7
  • if I try mkdir -m 555 -p r{,/s{,/t}} I receive twice: mkdir: cannot create directory ‘r/s’: Permission denied Commented Jan 7, 2022 at 0:07
  • 1
    Yes of course you will. Please read the last paragraph of my answer Commented Jan 7, 2022 at 0:10
  • Sorry, tired so far, I didn't read well that part - both commands work, but seems mandatory apply the verbose approach about repeat the names. It for complex/long paths would be something 'ugly' for scripts purposes. Is there a way to avoid that? Commented Jan 7, 2022 at 0:18
  • huge thanks for the extra code. Valuable your feedback for all the community. Commented Jan 7, 2022 at 12:32
  • Perhaps I've misunderstood your first paragraph, but mkdir -m 555 -p a a/b a/b/c fails under bash on two BSD variants I tested (FreeBSD and Mac OSX). FreeBSD reports: mkdir: a/b: Permission denied\nmkdir: a/b: Permission denied while Mac OSX reports: mkdir: a/b: Permission denied\nmkdir: a/b/c: Permission denied
    – Jim L.
    Commented Jan 10, 2022 at 21:47
2

It does look like mkdir doesn't apply the mode set by -m when it creates the intermediary directories. But, at least in most cases, you can use the umask to modify the permissions it sets for them.

The umask works by clearing the permission bits that are set in the umask, so if you want the intermediaries to have e.g. the permissions 0700, set umask to 0077. Using a subshell here to contain the umask change:

$ ( umask 0077; mkdir -p a/b )
$ ls -ld a
drwx------ 3 ilkkachu ilkkachu 4096 Jan  7 07:23 a/
$ ls -ld a/b
drwx------ 2 ilkkachu ilkkachu 4096 Jan  7 07:23 a/b/

Similarly, to get the permissions 0555, you'd set umask to 0222. But this doesn't actually work for the intermediaries, they are created with permissions 0755 instead. That is, the write permission for the owning user is added.

POSIX specifies for the permissions of the intermediate pathname components (see under the -p option):

[...] the mkdir utility shall create any pathname components of the path prefix of dir that do not name an existing directory [by equivalent of creating the missing directory] and then calling the chmod() function with the following arguments: [...]

 2. The value (S_IWUSR|S_IXUSR|~filemask)&0777 as the mode argument, where filemask is the file mode creation mask of the process.

(S_IWUSR and S_IXUSR are the C constants for those permissions bits.)

The GNU coreutils online manual says:

-p, --parents
Make any missing parent directories for each argument, setting their file permission bits to =rwx,u+wx, that is, with the umask modified by u+wx. Ignore existing parent directories, and do not change their file permission bits.

It goes on to say that the -m option doesn't apply to these, but that you can hence use the umask to control the permissions of them.

(Though the text there looks off, it mentions "The umask must include u=wx for this method to work", which seems to be missing the inverted sense of the umask.)


Presumably mkdir does that because otherwise it wouldn't be able to create the next directory inside the one just created without write permissions. The same happens for the access / search (x) permission bit, for the same reason.

So, if you want to create directories you can't write to or search yourself, it looks like you'll just have to chmod them after. Otherwise, setting umask for the mkdir works.

0

You could use the install command (not standard, from BSD in the 80s also included in GNU coreutils) as:

install -m 555 -d a/b/c a/b a

Or with shells with support for csh-style brace expansion:

install -m 555 -d a{/b{/c,},}

Note that if the directory components already existed beforehand, their permissions (the permissions of the directories they point to for symlinks) will be changed to 0555.

To only change the permissions of directory components that are being newly created, you could do it as a function. For instance, in zsh:

mkdirhierperm() {
  local mode=$1 dir=$2 dirs=()
  until [[ -d $dir ]] dirs[1,0]=$dir dir=$dir:h
  if (( $#dirs == 0 )); then
    (umask 77 && mkdir -p -- $dirs[-1]) &&
      chmod -- $mode $dirs
  fi
}

mkdirhierperm 555 a/b/c
mkdirhierperm 555 /full/path/to/some/new/dir
0

One might do this in 2 steps: first call mkdir to create the directories and then run find . -type d -exec chmod 555 {} \; to modify the modes.

You must log in to answer this question.

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