Skip to main content


'mkdir -p' lets you make a deeply nested subdirectory like a/b/c/d, making all the intermediate directories on the way to it. So if even 'a' doesn't exist, it'll make that, then a/b, etc.

But you can also get it to make multiple _non_-nested directories, because it accepts '..' in the path and doesn't treat it specially:

$ mkdir -p alpha/../beta/../gamma
$ ls
alpha beta gamma
$

[Edit: to be clear, I'm pointing out an amusing edge case, not giving advice!]

This entry was edited (1 year ago)

reshared this

in reply to Simon Tatham

That btw will blow up in interesting ways if one of them already exists and is a symlink 8)
in reply to The Penguin of Evil

@etchedpixels I can never tell when .. means to pop the last directory in the path I used to get here, or it means to follow the literal ".." directory entry of wherever-the-hell-I-am-now.
in reply to ROTOPE~1

@rotopenguin @etchedpixels yes, I've always hated the bash feature of 'helpfully' trying to interpret .. more like the naΓ―ve expectation, but only managing to do it for bash builtins, so that 'cd ../foo' and 'ls ../foo' are talking about different directories.

I do 'set -P' in my .bashrc, so that I get a consistent experience of .. going to the physical parent dir linked from my cwd's inode. I'd rather that than have to keep remembering which is which.

in reply to Simon Tatham

@Simon Tatham @ROTOPE~1 :yell: @The Penguin of Evil I’m reminded (somewhat tangentially) of the very different way VMS set default worked. IIRC you couldn’t use - as a path component; it was just a shorthand for manipulating what specification would be prepended to any unqualified filename.
in reply to Simon Tatham

Other than the obvious (because it's neat, and because you can), what does this offer over `mkdir -p alpha beta gamma` ?
in reply to Tom Hayward

@hattom I wasn't trying to suggest _using_ it – I was suggesting laughing at it, or being interested by it, or wondering if it was an oversight that it was allowed!
in reply to Simon Tatham

Thanks! I was just checking in case there was some funky benefit I was missing ;)
in reply to Simon Tatham

$ mkdir -p alpha/../beta/../alpha/beta/../alpha/beta/

$ find *
alpha
alpha/alpha
alpha/alpha/beta
alpha/beta
beta

I'm sure there is some sort of hidden art in this.

in reply to Simon Tatham

on most shells

mkdir -p ./{alpha,beta,gamma}

will lead to the same result as your last example

in reply to chaotic metal enby

@neijatolf I'm going to have to get better at leaving space in my toots to make it clear whether I'm suggesting a useful tip, or being amused by a weird edge case.

This was a 'weird edge case' toot. It's not a helpful pro tip!

Perhaps the worst effect of 'mkdir -p' permitting this syntax is that you can bamboozle other people's shell scripts into making unrelated directories as a side effect of their intended behaviour. I wonder if there are any actual security holes arising from that.

in reply to Simon Tatham

@neijatolf

In social media, all ambiguity is resolved in favor of whatever interpretation is most malevolent.

Unknown parent

mastodon - Link to source
Daniel Bohrer
@RogerBW TIL that this is a bashism 😲
(It's not mentioned anywhere in pubs.opengroup.org/onlinepubs/… )
in reply to Simon Tatham

it also silently passes an existing directory instead of returning a non-zero response, which can be useful.
in reply to Simon Tatham

I'm too lazy to look up the current edition, but the paper copy of POSIX-2001 under my desk seems to require this: "mkdir -p" is specified in terms of "dirname" and "dirname" has no special case for "..".
in reply to Ben Harris

@bjh21 of course, that need not stop GNU coreutils from making mkdir reject '..' anyway, if they were to decide it was a serious problem (say, if it gave rise to an actual security hole somewhere). That seems like exactly the kind of situation POSIXLY_CORRECT is for.
in reply to Simon Tatham

@bjh21 Presumably the mkdir implementation itself also doesn't include any special casing for '..'? So you'd have to *add* a special case to filter out directory path components with that name.
in reply to Dr. David McBride (dwm)

@dwm @bjh21 yes. And of course you do want to permit _some_ instances of '..', because 'mkdir ../foo' is perfectly sensible, and so is 'mkdir -p ../foo/bar'.

It's only after mkdir -p starts actually _making_ directories that '..' is weird. So I think if this needed changing and it were my job to do it, I'd do the check at the moment that 'mkdir -p' finds the first dir it actually needs to make: before making it, scan the tail of the input path for '..', and fail if you find one.

in reply to Simon Tatham

@bjh21 That would require lots of heavily breaking changes all over the place, this has nothing to do with mkdir specifically ..

consider "cat alpha/../beta/../gamma", where alpha and beta are directories under CWD, and gamma is a file.

in reply to Alfred M. Szmidt

@amszmidt @bjh21 no, it _is_ to do with mkdir specifically. The only thing I'm even _considering_ objecting to is pathnames that include a '..' component relative to a directory that _doesn't already exist_ at the time the pathname is specified. 'cat' is uncontroversial.

(And I'm not even _sure_ that this mkdir behaviour needs to be changed. This is all hypothetical, supposing that someone were to decide it did need to be changed.)

in reply to Simon Tatham

@bjh21 No. It has literally nothing to do with mkdir but the semantics of file names and how ./ and ../ behave.
in reply to Alfred M. Szmidt

@amszmidt @bjh21 I think you think "it" is something completely different from what I think it is!

I know very well that 'cat A/../B' isn't the same as 'cat B' because if A is a symlink then A/.. is the parent of its target, which may not be the cwd. I'm not objecting to it. That is fine, and I wouldn't change it.

I'm suggesting the possibility that 'mkdir -p A/../B' might report an error if A doesn't already exist, instead of making non-nested directories. I'm not suggesting a change to cat!

in reply to Simon Tatham

@bjh21 -p creates any missing directories irrespective of where they might reside, so if A does not exist it is created. It would be an error to behave in any other way. This is a misunderstanding of what -p does or is meant to do.
in reply to Alfred M. Szmidt

@amszmidt @bjh21 that is indeed the POSIX spec of 'mkdir -p'. We're discussing whether it _should_ be the spec.
in reply to Simon Tatham

@bjh21 of course it should? Anything else would be illogical and quite broken … it would not have the same semantics as anything else that works with directories.
in reply to Alfred M. Szmidt

@amszmidt @bjh21 I still don't see! The only other things with the semantics of 'mkdir -p' are analogues in library code, such as Python os.makedirs.

To be clear: if A already exists, I'm not proposing that 'mkdir -p A/../B' should stop working, or change what it does. Only if A _doesn't_ exist. Maybe.

Rationale: the use case for mkdir -p is to make nested dirs. I don't think it was intentional to also allow it to make them alongside each other. It may be harmless, but it's not the _purpose_.

in reply to Simon Tatham

@bjh21 If 'mkdir -p A/../B' is to be an error, then 'mkdir -p A/' needs to be an error. The way mkdir -p works is to move a pointer across the string, stopping at each '/', and doing a mkdir(2) sys call up to that point, the location of each directory is resolved by the file system.

But to treat this (a/../b/.. ..) as a special case you would need to almost go backwards, and check if the deepest directory exists, and then going up the tree.

in reply to Alfred M. Szmidt

not quite; GNU mkdir chdirs into each directory as it goes, so it doesn't reuse the whole path from the start each time.

It's true that if you mkdir, tolerate EEXIST, then chdir, then my strategy elsethread doesn't work, because you've done the first actual modification before you find out you'd rather not have done. It would only work if you swapped it round: chdir, tolerate ENOENT, mkdir.

(There's a tiny race condition, but that's true of the current approach too.)

This entry was edited (1 year ago)
in reply to Simon Tatham

@bjh21 The algorithm I described is the "traditional" one. GNU mkdir does a bit more, yes .. but the end result is essentially the same. E.g., BSD does it the 'old fashioned' way.
in reply to Alfred M. Szmidt

@amszmidt @bjh21 but my point, anyway, is that you don't have to process backwards. You just notice the switchover point when you start creating.

E.g. 'mkdir A/../B/../C', if A exists and B doesn't:

chdir to A βœ“
chdir to .. βœ“
chdir to B β†’ ENOENT. So we're asked to make B/../C. Is there a '..' component anywhere in the remaining part of the path? Yes, so fail.

Otherwise, we'd switch to mkdir/chdir on each component, still tolerating EEXIST in case someone else was doing the same in parallel.

in reply to Simon Tatham

@bjh21 Which is overly complicated, and not how it is normally done. I forgot why we started using chdir() in GNU mkdir.

github.com/dspinellis/unix-his…

Unknown parent

mastodon - Link to source
Ben Zanin

@RogerBW @daniel_bohrer didn't bash more or less crib brace expansion from ksh93?

I could be misremembering, a quick scan of mywiki.wooledge.org/BashFAQ/06… didn't confirm this; I'm going to go peek at commit logs for a bit

in reply to Simon Tatham

the reverse
$ rmdir -p alpha/../beta/../gamma
doesn't work, because the .. isn't empty πŸ™
in reply to Simon Tatham

Always been a fan of "mkdir -p {a,b,c}/{a,b,c}/{a,b,c}". Once used that to teach a beginners class how to move around through directories on the command line to find a file I nested deeply.
in reply to Simon Tatham

Or just pass 3 arguments …

mkdir -p {alpha,beta,gamma}

The -p isn't even strictly necessary in this case.

in reply to BenBE

@benbe I've edited the post to be clearer that I wasn't suggesting this as useful advice.
in reply to Simon Tatham

Sure, and my post demonstrates another syntax you'd probably use rarely in production either …
in reply to Simon Tatham

So you can technically do `mkdir -p tmp/.../tmp` if you want to make sure it creates `./tmp` πŸ˜†
This entry was edited (1 year ago)
Unknown parent

mastodon - Link to source
scy

@neingeist you mean like this gem that can be used as a workaround if your sed doesn't have `-i`?

{ rm file.txt; sed '…' > file.txt ; } < file.txt

David JONES reshared this.

in reply to scy

@scy @neingeist for that I use a tiny utility I wrote called 'reservoir', which reads and buffers input until EOF, then writes it all out again. With the -o option, you can tell it a file to overwrite. So you can do this kind of thing, without depending on your filter tool having an option that works like sed -i:

$arbitrary_filtering_command < filename | reservoir -o filename

chiark.greenend.org.uk/~sgtath…
chiark.greenend.org.uk/~sgtath…

in reply to scy

@scy @neingeist looks very similar, yes! This is definitely in the class of programs so simple that it's quicker to write it when you need it than look around for someone who's already written one.

Comparing the two: reservoir has the safety-catch of (by default) not overwriting the file with one of length 0, so that if the filtering process fails to start at all, you haven't blown away your input. On the other hand, sponge has an append mode, and also attempts atomic file replacement.

in reply to Simon Tatham

NORMAL PEOPLE: $ mkdir alpha beta gamma

YOU: $ mkdir -p alpha/../beta/../gamma

ME: $ for d in alpha beta gamma; do mkdir $d; done

in reply to Simon Tatham

"let's just call `mkdir some_folder/some_unvalidated_user_input` what's the worst that could happen?"
in reply to Simon Tatham

So what you're saying is in theory I could do something like

mkdir -p root/zero/one/two/../../one_a/../one_b/../one_c/two_a/three/../three_a/../../../../zero_a/one_c/two_b

This entry was edited (1 year ago)
in reply to Simon Tatham

Nice! Of course (for anyone who isn't aware) you can also just do "mkdir alpha beta gamma" to save on typing πŸ˜‚
in reply to Simon Tatham

Fun, though mkdir allows multiple arguments without resorting to tricks.
in reply to Simon Tatham

It's really cursed

$ mkdir -p a/b/{../{.,..},.}/c
$ tree
.
β”œβ”€β”€ a
β”‚ β”œβ”€β”€ b
β”‚ β”‚ └── c
β”‚ └── c
└── c

Now, I'm thinking if there's an elegant way to make it draw a Pascal triangle πŸ€”

in reply to Simon Tatham

neat trick, but makes more sense to have your shell do the expansion for you.
`mkdir /some/long/prefix/{alpha,bravo,charlie}`
in reply to mr64bit

@mr64bit but where did you get the idea that I was interested in the way to do it that made the _most_ sense?
in reply to sn πŸ¦β€β¬›

@sn sorry to pick on you in particular, you're just one of the last 5 replies who's asked this, but … have I still not made this clear enough?

I don't do this on purpose. I'm not advising anyone else to.

I'm pointing out an unexpected edge case, because it's useful to know those exist, even if they're not useful.

Particularly, if you have a script that runs mkdir on one of its arguments, then a user can make it create extra directories as a side effect, and you might want to prevent that!

in reply to Alexandra Lanes

@ajlanes good thought – hadn't occurred to me.

@sn, if that's true, then I apologise again for being tetchy!

Unknown parent

mastodon - Link to source
Simon Tatham

@joeyh @hattom I think the "why don't you just use bash brace expansion" brigade in this thread will still say you should have done this instead:

mkdir -p a/b/c/d/e/f/g/h/{x,y,z}

but perhaps a counterargument is that a/b/c/d/e/f/g/h/x/../y/../z will only follow the long common initial path once, meaning better performance and fewer race conditions πŸ˜€

in reply to Simon Tatham

I do not think you should expect the same behaviour across all implementations of coreutils...
in reply to Simon Tatham

Took me a few moments to realise why it reminded me of this picture. In one case it turns out that a sibling of a directory can also be its parent. In the other it turns out that cousins can be genetic siblings.
⇧