23

In system call open(), if I open with O_CREAT | O_EXCL, the system call ensures that the file will only be created if it does not exist. The atomicity is guaranteed by the system call. Is there a similar way to create a file in an atomic fashion from a bash script?

UPDATE: I found two different atomic ways

  1. Use set -o noclobber. Then you can use > operator atomically.
  2. Just use mkdir. Mkdir is atomic
3
  • the system call ensures that the file will only be created if it does not exist Hmpf. If the file does not exists, it will be created. If it exists, the system-call will fail. Commented Dec 11, 2012 at 21:18
  • You could try mktemp to create a file, then try to mv it to the desired name. Commented Dec 11, 2012 at 21:38
  • 2
    From where did you get that "Mkdir is atomic"? The man page doesn't say anything about it (not) being atomic. Also, the man page does not claim that the noclobber option is atomic. Where did you get that from? Commented Aug 24, 2020 at 23:32

6 Answers 6

35

A 100% pure bash solution:

set -o noclobber
{ > file ; } &> /dev/null

This command creates a file named file if there's no existent file named file. If there's a file named file, then do nothing (but return a non-zero return code).

Pros of > over the touch command:

  • Doesn't update timestamp if file already existed
  • 100% bash builtin
  • Return code as expected: fail if file already existed or if file couldn't be created; success if file didn't exist and was created.

Cons:

  • need to set the noclobber option (but it's okay in a script, if you're careful with redirections, or unset it afterwards).

I guess this solution is really the bash counterpart of the open system call with O_CREAT | O_EXCL.

Sign up to request clarification or add additional context in comments.

7 Comments

Is "> file" guaranteed to be atomic?
(set -o noclobber;>file) &>/dev/null does the same, but doesn't affect the noclobber option in the current shell.
@srcerer From the reference: If the redirection operator is ‘>’, and the noclobber option to the set builtin has been enabled, the redirection will fail if the file whose name results from the expansion of word exists and is a regular file.
@Socowi The use of O_EXCL in the open should make it atomic, no? I'd guess "hopefully" means it's atomic assuming the filesystem implements O_EXCL properly.
@potato Thank you for the remark. Looking at the source code (and not just focusing on the comments) today, it seems you are right. For bystanders: man open says If O_CREAT and O_EXCL are set, open() shall fail if the file exists. It also works for other special cases mentioned in bash's source code, e.g. If [...] path names a symbolic link, open() shall fail. ¶ I keep my original comment so that our discussion has context and helps future readers of bash's source code :)
|
5

Here's a bash function using the mv -n trick:

function mkatomic() {
  f="$(mktemp)"
  mv -n "$f" "$1"
  if [ -e "$f" ]; then
    rm "$f"
    echo "ERROR: file exists:" "$1" >&2
    return 1
  fi
}

Examples:

$ mkatomic foo
$ wc -c foo
0 foo
$ mkatomic foo
ERROR: file exists: foo

2 Comments

At least in GNU coreutils 8.28 mv -n itself is not atomic, but in 8.32 it seems to be ok (or at least safer). Maybe this change fixed it. You can test your mv using while :; do rm -f c; echo a > a; echo b > b; mv b c & mv -n a c & wait; [ "$(< c)" = b ] || break; done; echo "mv -n is not atomic". If this ever terminates then your mv -n is not atomic. If it continues to run it still could be non-atomic; but the longer it runs the more likely it is that mv -n is indeed atomic.
I would be wary of the intricate details of this solution. The problem is that it inherently copies files across different filesystems. For just a creation check it's fine, but if a similar code is used to move "important" files, things might go bad due to the non-atomicity of the move.
3

You could create it under a randomly-generated name, then rename (mv -n random desired) it into place with the desired name. The rename will fail if the file already exists.

Like this:

#!/bin/bash

touch randomFileName
mv -n randomFileName lockFile

if [ -e randomFileName ] ; then
    echo "Failed to acquired lock"
else
    echo "Acquired lock"
fi

2 Comments

My "mv" is not atomic. It first uses "stat" to test if the target file exists, and if it does not it uses "rename". But "rename" will simply overwrite the target if it existed. So there is a small time frame (between the "stat" and "rename" call) in which a file created with the name of the destination would be overwritten.
Ah, interesting. mv won't help at all, then, sorry.
2

Just to be clear, ensuring the file will only be created if it doesn't exist is not the same thing as atomicity. The operation is atomic if and only if, when two or more separate threads attempt to do the same thing at the same time, exactly one will succeed and all others will fail.

The best way I know of to create a file atomically in a shell script follows this pattern (and it's not perfect):

  1. create a file that has an extremely high chance of not existing (using a decent random number selection or something in the file name), and place some unique content in it (something that no other thread would have - again, a random number or something)
  2. verify that the file exists and contains the contents you expect it to
  3. create a hard link from that file to the desired file
  4. verify that the desired file contains the expected contents

In particular, touch is not atomic, since it will create the file if it's not there, or simply update the timestamp. You might be able to play games with different timestamps, but reading and parsing a timestamp to see if you "won" the race is harder than the above. mkdir can be atomic, but you would have to check the return code, because otherwise, you can only tell that "yes, the directory was created, but I don't know which thread won". If you're on a file system that doesn't support hard links, you might have to settle for a less ideal solution.

3 Comments

The open syscall supports a flag O_EXCL, which when combined with O_CREAT, will cause the open call to fail if the file already exists. This is an atomic operation.
@RyanPatterson Correct. But you can't call open() with O_EXCL directly from a bash script, which is what the original question was about.
The { > file; } construct guarantees atomicity, at least in POSIX-compliant shells. You can switch bash to POSIX-compliant mode, but even without that, bash does not deviate from POSIX with respect to redirection atomicity.
0

Another way to do this is to use umask to try to create the file and open it for writing, without creating it with write permissions, like this:

LOCK_FILE=only_one_at_a_time_please
UMASK=$(umask)
umask 777
echo "$$" > "$LOCK_FILE"
umask "$UMASK"
trap "rm '$LOCK_FILE'" EXIT

If the file is missing, the script will succeed at creating and opening it for writing, despite the file being created without writing permissions. If it already exists, the script won't be able to open the file for writing. It would be possible to use exec to open the file and keep the file descriptor around.

rm requires you to have write permissions to the directory itself, without regards to file permissions.

Comments

-5

touch is the command you are looking for. It updates timestamps of the provided file if the file exists or creates it if it doesn't.

2 Comments

Except this doesn't fail when the file already exists, as O_EXCL does.
I haven't looked at the touch source, but it may not atomic. Otoh, it changes both access and modification time if the file exists.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.