'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
The Penguin of Evil
in reply to Simon Tatham • • •ROTOPE~1
in reply to The Penguin of Evil • • •Simon Tatham
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.
Alexandra Lanes
in reply to Simon Tatham • •Volker Stolz
in reply to Simon Tatham • • •Tom Hayward
in reply to Simon Tatham • • •Simon Tatham
in reply to Tom Hayward • • •Alexandra Lanes likes this.
Tom Hayward
in reply to Simon Tatham • • •Winnie π³οΈβπ
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.
chaotic metal enby
in reply to Simon Tatham • • •on most shells
mkdir -p ./{alpha,beta,gamma}
will lead to the same result as your last example
Simon Tatham
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.
Alexandra Lanes
in reply to Simon Tatham • •like this
kæt and sabik like this.
Professor_Stevens
in reply to Simon Tatham • • •@neijatolf
In social media, all ambiguity is resolved in favor of whatever interpretation is most malevolent.
Daniel Bohrer
Unknown parent • • •(It's not mentioned anywhere in pubs.opengroup.org/onlinepubs/β¦ )
Shell Command Language
pubs.opengroup.orgMrs Beanbag
in reply to Simon Tatham • • •draeath
in reply to Simon Tatham • • •Ben Harris
in reply to Simon Tatham • • •Simon Tatham
in reply to Ben Harris • • •Dr. David McBride (dwm)
in reply to Simon Tatham • • •Simon Tatham
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.
Alfred M. Szmidt
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.
Simon Tatham
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.)
Alfred M. Szmidt
in reply to Simon Tatham • • •Simon Tatham
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!
Alfred M. Szmidt
in reply to Simon Tatham • • •Simon Tatham
in reply to Alfred M. Szmidt • • •Alfred M. Szmidt
in reply to Simon Tatham • • •Simon Tatham
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_.
Alfred M. Szmidt
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.
Simon Tatham
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.)
Alfred M. Szmidt
in reply to Simon Tatham • • •Simon Tatham
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.
Alfred M. Szmidt
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β¦
unix-history-repo/usr/src/bin/mkdir/mkdir.c at 1d33b1148ce80b9b3332d3e497469df7d82a8ee1 Β· dspinellis/unix-history-repo
GitHubBen Zanin
Unknown parent • • •@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
BashFAQ/061 - Greg's Wiki
mywiki.wooledge.orgπΈ lily π³οΈββ§οΈ ΞΈΞ β & β
in reply to Simon Tatham • • •πΈ lily π³οΈββ§οΈ ΞΈΞ β & β
in reply to πΈ lily π³οΈββ§οΈ ΞΈΞ β & β • • •Sensitive content
mkdir a b c
Simon Tatham
in reply to πΈ lily π³οΈββ§οΈ ΞΈΞ β & β • • •Leah Neukirchen
in reply to Simon Tatham • • •$ rmdir -p alpha/../beta/../gamma
doesn't work, because the .. isn't empty π
kyle
in reply to Simon Tatham • • •kae
in reply to Simon Tatham • • •Simon Tatham
in reply to kae • • •kae
in reply to Simon Tatham • • •BenBE
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.
Simon Tatham
in reply to BenBE • • •BenBE
in reply to Simon Tatham • • •Matt Papakipos
in reply to Simon Tatham • • •Alexander Knochel
in reply to Simon Tatham • • •Timothy Wolodzko
in reply to Simon Tatham • • •scy
Unknown parent • • •@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.
Simon Tatham
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β¦
Simon Tatham's miscellaneous utilities collection
www.chiark.greenend.org.ukscy
in reply to Simon Tatham • • •@neingeist Isn't that basically sponge(1)?
joeyh.name/code/moreutils/
moreutils
joeyh.nameSimon Tatham
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.
Shane Celis
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
alyx (dual-stack)
in reply to Simon Tatham • • •Cody
in reply to Simon Tatham • • •Squid!
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
mal3aby
in reply to Simon Tatham • • •Steve Hill π΄σ §σ ’σ ·σ ¬σ ³σ ΏπͺπΊ
in reply to Simon Tatham • • •Mans R
in reply to Simon Tatham • • •Timothy Wolodzko
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 π€
mr64bit
in reply to Simon Tatham • • •`mkdir /some/long/prefix/{alpha,bravo,charlie}`
Simon Tatham
in reply to mr64bit • • •sn π¦ββ¬
in reply to Simon Tatham • • •Simon Tatham
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!
Alexandra Lanes
in reply to Simon Tatham • •Simon Tatham likes this.
Simon Tatham
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!
Simon Tatham
Unknown parent • • •@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 π
Yaksh Bariya
in reply to Simon Tatham • • •Taffer π¨π¦
in reply to Simon Tatham • • •Dan Piponi
in reply to Simon Tatham • • •Pippin likes this.
Pxl Phile
in reply to Simon Tatham • • •