'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 (Monday, June 10, 2024, 4:13 PM)

reshared this

in reply to 👻 A spooky number 4 👻

@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 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 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 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 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 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 (Tuesday, June 11, 2024, 2:46 PM)
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…

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 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!

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 😀

⇧