Question

Okay so after spending ages developing a script ready for testing, I've now learned the target environment doesn't have mkstemp (I foolishly believed every unix-y OS had that), nor does it seem to have any of the other common temp-file utilities. If there are any other widely available temp-file commands do let me know, but I don't think I have any to use.

So, this means I need to implement a form of mkstemp myself. Now the easiest way is something like tmp="/tmp/tmp.$$.$RANDOM" and while that works it doesn't guarantee there won't be a collision of file names, so I need to test for that, however the problem is that between testing for the file, and creating one, the file could end up unexpectedly being created, so it may not be a suitable method either, at least on it's own.

In the past I had to implement an equivalent to lockfile myself, which I was able to do by using a temporary file moved into place using mv as a cheat (if it returns an error then the lock already existed). I'm inclined to think I can maybe do something similar using some operation that will fail if the file already exists, but I'm not sure what the best way would be.

I know that use of /tmp/tmp.$$.$RANDOM is unlikely to result in collisions, but I'd like to implement this correctly if I can, as the script needs to create quite a lot of temporary files that are then moved into place, and I may need to do the same in other scripts later on, so it'd be nice to do it correctly!

EDIT: I just realised I've been referring to mktemp everywhere instead of mkstemp which is the one I really want to replicate (where a file is created safely for you). I think I've corrected mistaken mentions, please forgive the confusion!

Était-ce utile?

La solution

Usually, the command line tool for creating temporary directories is called mktemp, not mkstemp. I'm not sure if it's safe to use on all platforms. If it isn't, you can attempt to make a directory with a strict umask and fail when the directory already exists.

mkdtemp() {
    old_mask=$(umask)
    umask 077

    name="/tmp/tmp.$$.$RANDOM"
    mkdir "$name" && echo "$name"
    retval=$?

    umask $old_mask

    return $retval
}

Usage:

tempdir=$(mkdtemp) || report_failure

Since this tries to create the directory (an atomic operation) instead of checking whether it exists and fails when the name it generates is taken, this operation is safe. It is prone to a denial-of-service attack, though, where an attacker creates many temporary directories just to let the above function fail.

Autres conseils

Using @larsmans's answer I think I may have arrived at one of my own. Instead of using mkdir I'm going to use cp on a known file, specifically /dev/null, which effectively creates an empty file, but should do so atomically!

For those interested here's the simplified version of the code (please forgive any typos):

mkstemp() {
    umask_old=$(umask)
    umask 077

    name="/tmp/tmp.$$.$RANDOM"
    cp -n /dev/null "$name" 2> /dev/null && echo "$name"
    retval=$?

    umask "$umask_old"
    return "$retval"
}

I've simplified the above to match larsmans's, as my actual solution will loop until a file is created or a limit on attempts is reached, and I'm using behaviour that more closely matches how mkstemp actually picks names (using a template with trailing X's), but I think the above shows what I'm doing, and it seems to work exactly as I require!

So thanks larsmans, your solution is a fine stand-in for mkdtemp and the above should do the same as a stand-in for mkstemp. I will probably mark this as the answer, but I'll leave it for a little while in case anyone notices any problems with the code.

EDIT: Unfortunately this method requires a version of cp with the not widely supported -n flag (do not overwrite), but even this won't always work if the target file exists but is empty, as cp will consider it to already be a valid copy and exit with a status of 0. For this reason mkdir may still be the better option.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top