28

In Bash, I'm trying to make a function getLock to be used with different lock names.

function getLock
{
    getLock_FILE="${1}"
    getLock_OP="${2}"
    case "${getLock_OP}" in
        "LOCK_UN")
            flock -u "${getLock_FILE}"
            rm -fr "${getLock_FILE}"
            ;;
        "LOCK_EX")
            flock -x "${getLock_FILE}"
    esac
}

But flock says flock: bad number: myfilelock

How can I just lock a file, and release it when I want, without having to execute a command in the flock?

It is to be used like this:

getLock myfilelock LOCK_EX
somecommands
........
getLock myfilelock LOCK_UN
codeforester
  • 39,467
  • 16
  • 112
  • 140
JorgeeFG
  • 5,651
  • 12
  • 59
  • 92
  • @Jite I don't understand you. I don't wan't to lock a file from being modified, just want to get a lock on it so another process running in PHP that locks the same file, waits for it to be released. – JorgeeFG Jun 24 '14 at 13:41
  • Redesign your script to use `flock` for every instance that needs to be lock-protected. Otherwise just create your own lock files, it's just regular files... – Jite Jun 24 '14 at 13:41
  • So `flock` works like: process 1 executes `flock lockfile command_to_run`, process 2 executes `flock lockfile some_command`. Now the second process will be held up until the first one finishes. Simple as that. No need to even care about the lockfile itself, `flock` handles that automagically. – Jite Jun 24 '14 at 13:43
  • 3
    `flock` locks file descriptors, not files. That's important, because it means that you **cannot** lock a file you aren't holding open for the duration of the lock. – Charles Duffy Jun 24 '14 at 14:40
  • 2
    FYI, the `function` keyword is gratuitously incompatible with POSIX sh, and adds no value whatsoever over the POSIX syntax for declaring functions. Also, why use namespaced global variables when you can just use locals? Also, NEVER NEVER NEVER NEVER delete lockfiles created with flock (unless you're *completely* sure nobody could be trying to grab a lock at the same time -- so on reboot is safe); this creates race conditions. Leave the unlocked files on-disk to ensure that if two programs try to concurrently grab the same released lock, they get the same inode. – Charles Duffy Jun 24 '14 at 14:46
  • @CharlesDuffy I thought there were no local variables, do you have to use "declare" for them to be local? Thats why I used namespaces. And I should not use function keyword you say? Just name and braces? – JorgeeFG Jun 24 '14 at 14:56
  • @Jorge, `declare` will make things local if used inside a function, but there's also a `local` keyword available. – Charles Duffy Jun 24 '14 at 14:57
  • @Jorge, ...and correct, just name and braces; the `function` keyword makes your code incompatible with other shells, but (unlike many other, more useful bashisms) adds no value in doing so. – Charles Duffy Jun 24 '14 at 16:12

1 Answers1

53

To lock the file:

exec 3>filename # open a file handle; this part will always succeed
flock -x 3      # lock the file handle; this part will block

To release the lock:

exec 3>&-       # close the file handle

You can also do it the way the flock man page describes:

{
  flock -x 3
  ...other stuff here...
} 3>filename

...in which case the file is automatically closed when the block exits. (A subshell can also be used here, via using ( ) rather than { }, but this should be a deliberate decision -- as subshells have a performance penalty, and scope variable modifications and other state changes to themselves).


If you're running a new enough version of bash, you don't need to manage file descriptor numbers by hand:

# this requires a very new bash -- 4.2 or so.
exec {lock_fd}>filename  # open filename, store FD number in lock_fd
flock -x "$lock_fd"      # pass that FD number to flock
exec {lock_fd}>&-         # later: release the lock

...now, for your function, we're going to need associative arrays and automatic FD allocation (and, to allow the same file to be locked and unlocked from different paths, GNU readlink) -- so this won't work with older bash releases:

declare -A lock_fds=()                        # store FDs in an associative array
getLock() {
  local file=$(readlink -f "$1")              # declare locals; canonicalize name
  local op=$2
  case $op in
    LOCK_UN)
      [[ ${lock_fds[$file]} ]] || return      # if not locked, do nothing
      exec {lock_fds[$file]}>&-              # close the FD, releasing the lock
      unset lock_fds[$file]                   # ...and clear the map entry.
      ;;
    LOCK_EX)
      [[ ${lock_fds[$file]} ]] && return      # if already locked, do nothing
      local new_lock_fd                       # don't leak this variable
      exec {new_lock_fd}>"$file"              # open the file...
      flock -x "$new_lock_fd"                 # ...lock the fd...
      lock_fds[$file]=$new_lock_fd            # ...and store the locked FD.
      ;;
  esac
}

If you're on a platform where GNU readlink is unavailable, I'd suggest replacing the readlink -f call with realpath from sh-realpath by Michael Kropat (relying only on widely-available readlink functionality, not GNU extensions).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • exec 3>filename deletes all the file in my cwd – Himanshu Poddar Apr 07 '21 at 10:39
  • 1
    @HimanshuPoddar, yes, it's expected to truncate data in `filename` You should use it for a _lockfile_. Lockfiles don't have contents (so it's typical to have access to a file `data` controlled by `.data.lock`). That said, if you don't want it to truncate contents, change from `3>"$file"` to `3>>"$file"`. – Charles Duffy Apr 07 '21 at 13:50
  • 1
    @HimanshuPoddar, ...to be clear, it doesn't delete _all_ files; it'll only overwrite contents of the _one_ file in `filename`. If you can generate a contrary [mre] (ideally something that can be demonstrated in an online sandbox like https://replit.com/languages/bash), please open a new question, and feel free to @ me in. – Charles Duffy Apr 07 '21 at 13:52
  • `exec $lock_fd>&-` doesn't work. It complains `bash: exec: 10: not found` and closes stdout. See https://stackoverflow.com/questions/8295908/how-to-use-a-variable-to-indicate-a-file-descriptor-in-bash for the workaround. – Barmar Oct 20 '22 at 16:01
  • @Barmar, `{lock_fd}>&-` clearly (haven't read the link, but that line is obviously wrong on its face at a glance). You'll notice that I was already using the correct syntax elsewhere in this answer. – Charles Duffy Oct 20 '22 at 16:06
  • Yes, just found it in the manual. It says that for `&-`, `{variable}` uses the variable's value instead of assigning the variable. – Barmar Oct 20 '22 at 16:11