Bit of a conflicting requirement! Idiomatically, you put unique files either into $TMPDIR
(or /tmp by default), or they are state-carrying files, in which case they belong in $XDG_STATE_HOME/yourapplicationname/
(which defaults to $HOME/.local/state/yourapplicationname/
).
The point is - I'll do the creation myself.
Now, that's a bad idea! Just let mktemp
create the file, and then overwrite it with what you want. Letting mktemp
create it makes sure you're actually using a unique thing that hasn't been used before¹, e.g. by an interrupted execution of your script, or a second script.
In either case, you do use mktemp
, with the -p
option, e.g., something like
filename="$(mktemp -p "${XDG_STATE_HOME:-$HOME/.local/state}/einpoklumsscript/")"
But that's a safe temporary file name, and that's not really the same as a unique filename. For that, you'd usually want a UUID (universally unique identifier) and you can generate one using uuidgen
. But: unlike mktemp
, that's not race-safe. It seems you wantmktemp
, really!
¹ For your technical interest why this is the probably only way to create a filename that's safe against collisions even when running multiple instances of your script:
mktemp
creates that temporary file by calling openat(AT_FDCWD, "/path/to/random/filename", O_RDWR|O_CREAT|O_EXCL)
.
And the exciting part here is O_RDWR|O_CREAT|O_EXCL
:
O_RDWR
ensures the file is only created if the result can be both read and written to.O_CREAT
allows creation of the file by opening it,O_EXCL
enforces that this file is created; if it existed before, openat
fails, and mktemp
picks a different file name.
The UNIX filesystem API guarantees that two tasks, even running exactly synchronously, cannot O_EXCL
open the same file name – thus that's the operating system-supplied only way to ensure a file is created only once. Anything else is a race condition!
So, both correctlyandidiomatically: use mktemp
to create a temporary file that you then write into. That's the UNIX way of doing it! Don't create it yourself.
(a symlink in my case, or a named pipe, or something similar) in my filesystem, […] I'll have do the creation myself.
Luckily, that's not strictly true. Aside from openat(… , O_CREAT|O_EXCL)
, the renameat
syscall is also atomic.
So, the race-condition-safe dance becomes, if you're in a shell script:
filename="$(mktemp -p "${directory}")"
to "reserve" the filename, safely- replace that file with the thing you want to create, atomically:
- if the thing you want to replace it with is a symlink,
ln -f -s -- whatever "${filename}"
does this correctly atomically (by creating a randomly-named symlink, and renameat
-ing it to ${filename}
). I suspect that's what you want here. - if the thing you want to replace it with is a named pipe (or anything else for which the creation tool doesn't allow forcing replacement):
First, you create a local temporary directory safely on the same filesystem, localtmp="$(mktemp -d -p "${directory}")"
,
then you create a named pipe in there, mkfifo -- "${localtmp}/fifo"
,
then you rename that to the target mv -- "${localtmp}/fifo" "${filename}"
, and
finally, you remove the empty temporary directory rmdir -- "${directory}"
That's it! Admittedly, a bit more work, but that's the way you can have safely randomly named anything in a shell script, so it was worth writing down :)