Mark
Mark

Reputation: 263

Exclusive and shared locks in bash

I am trying to lock a file using bash, and I have managed to do so, but I am not sure if I am doing it the right way.

The following piece of code should try to write into the lock.log file and if it fails it should exit with exit status -1. If it can lock the file, then it should print the order number, unlock the file, and exit.

#!/bin/bash

function myfunc {
    (
        flock -e -n 200 || { echo "$1 - can't reach"; exit -1; }
        echo $1 >> $2 #critical
        sleep 2
        flock -u 200
    ) 200>$2 #critical
    echo "$1 -> end."
    exit 0
}

lock="lock.log"
for ((i=0; i < 10; i++)); do
    myfunc $i $lock &
done

exit 0

I could not understand why it would print on the standard output and the 2 lines that are tagged as critical are not quite clear to me, so I kindly ask for some explanation and maybe for a better example.

There may be some similar questions, but none with the basic things that I am asking - the usage of flock

Thank you

Upvotes: 1

Views: 925

Answers (1)

gniourf_gniourf
gniourf_gniourf

Reputation: 46853

Let's start with the 200>"$2" part:

When myfunc is called Bash sees this:

(
    ... stuff irrelevant for now ...
) 200>"$2"

so it opens file descriptor number 200 and will redirect it to the file obtained by expansion of $2. In your case, it's exactly like:

(
    ... stuff irrelevant for now ...
) 200>lock.log

By the way, file lock.log is at this point opened and truncated (i.e., its content, if any, is cleared).

To see this, try the following:

( ls /dev/fd ) 200>lock.log

You'll see that there's a “file” called 200 in there (at this point, try it with a lock.log file that has some content, and you'll see that after that its content disappeared) but compare with

ls /dev/fd

where you very likely only see 0, 1, 2, 3. For fun, try:

( ls /dev/fd >&200 ) 200>lock.log

This time nothing is shown on your terminal screen, but you'll see that the content of ls is in the file lock.log (with its previous content, if any, deleted). That's because with >&200 we redirected the standard output of ls to the file descriptor 200 which is /dev/fd/200, and this is then redirected to the file lock.log.

I guess this explains one of the critical lines.

Now what happens inside the subshell? I'm no flock expert, so bear with me.

With the argument 200, you're telling flock to use the file descriptor 200 (i.e., the “file” /dev/fd/200) as a lock file. When you call flock 200, flock tries to get a hold on the file. If it can, all is good, it keeps the grasp on it, otherwise it either fails (if given the -n option, which is your case) or it waits until someone else let the file go.

In case of failure, you're outputting the can't reach message to standard output and exit the subshell, and then you're executing the remaining part of the function (namely, the message end). Now, in case of success you're echoing the first argument passed to the function (here a number) and you're redirecting it to file lock.log. Does that explain the second critical line?

At the end of this script, you'll see that the file lock.log is empty. That's because the first call to myfunc succeeds, but all subsequent calls will truncate the file lock.log (rings a bell with you?). If you want to see some content inside lock.log, either call your function only once (so that no subsequent calls truncate the file), or open the file in append mode:

(
    ... the flock stuff ...
) 200>>"$2" # notice the >> instead of >

Like so you'll see the first lock that succeeded (not necessarily the number 0, as there are race conditions).

Upvotes: 2

Related Questions