Linux flock,如何“仅”锁定文件?

28
在Bash中,我试图创建一个函数getLock用于不同的锁名称。
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
}

但是flock报错:flock: bad number: myfilelock

我该如何在不需要执行flock命令的情况下锁定文件,并在需要时释放它?

用法如下:

getLock myfilelock LOCK_EX
somecommands
........
getLock myfilelock LOCK_UN

@Jite,我不明白你的意思。我不想锁定文件以防止其被修改,只是想在文件上获取一个锁,以便在 PHP 中运行的另一个进程等待该锁被释放。 - JorgeeFG
重新设计你的脚本,使用flock来保护每个需要锁定保护的实例。否则,只需创建自己的锁文件,这只是普通的文件... - Jite
flock 的工作方式如下:进程1执行 flock lockfile command_to_run,进程2执行 flock lockfile some_command。现在第二个进程将被阻塞,直到第一个进程完成。就这么简单。甚至不需要关心锁文件本身,flock 会自动处理。 - Jite
3
“flock”锁定文件描述符,而不是文件本身。这很重要,因为它意味着您无法锁定在锁定期间未打开的文件。 - Charles Duffy
2
FYI,function关键字与POSIX sh不兼容,并且与使用POSIX语法声明函数相比没有任何价值。此外,为什么要使用命名空间全局变量,而不是只使用本地变量呢?另外,永远不要删除使用flock创建的锁文件(除非您完全确定没有人在同时尝试获取锁 - 因此在重新启动时是安全的);这会创建竞争条件。将未锁定的文件保留在磁盘上,以确保如果两个程序尝试同时获取相同的已释放锁,则它们获得相同的inode。 - Charles Duffy
显示剩余3条评论
1个回答

54
锁定文件的方法:
exec 3>filename # open a file handle; this part will always succeed
flock -x 3      # lock the file handle; this part will block

释放锁定:
exec 3>&-       # close the file handle

你也可以按照群集手册中描述的方式来做。
{
  flock -x 3
  ...other stuff here...
} 3>filename

在这种情况下,当代码块退出时,文件会自动关闭。(也可以使用子shell,在这里使用( )而不是{ },但这应该是一个有意识的决定——因为子shell会带来性能损失,并且作用域变量修改和其他状态变化对它们自己产生影响)。
如果你使用的是足够新的bash版本,就不需要手动管理文件描述符号码了。
# 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

现在,对于您的函数,我们将需要关联数组和自动FD分配(以及为了允许从不同路径锁定和解锁相同文件,还需要GNU readlink)- 所以这在旧版本的bash中无法工作。
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
}

如果您所在的平台上无法使用GNU readlink,则建议使用sh-realpath by Michael Kropat中的realpath替换readlink -f调用(仅依赖于广泛可用的readlink功能,而非GNU扩展)。

exec 3>filename 删除了我当前工作目录中的所有文件。 - Himanshuman
1
@HimanshuPoddar,是的,在“filename”中截断数据是预期的。你应该将其用于_lockfile_。Lockfile 没有内容(因此通常可以访问由“.data.lock”控制的文件“data”)。也就是说,如果您不想截断内容,请将“3>“$file””更改为“3>>"$file"”。 - Charles Duffy
1
@HimanshuPoddar,... 要明确的是,它不会删除 所有 文件; 它只会覆盖 filename 中一个文件的内容。如果您能生成相反的 [mre](理想情况下,可以在 https://replit.com/languages/bash 这样的在线沙箱中演示),请打开一个新问题,并随时 @ 我。 - Charles Duffy
exec $lock_fd>&- 不起作用。它会抱怨 bash: exec: 10: not found 并关闭标准输出。请参见 https://dev59.com/aGsy5IYBdhLWcg3w0RXT 以获取解决方法。 - Barmar
@Barmar,{lock_fd}>&- 显然是错误的(虽然我没有阅读链接,但这一行在一瞥之间就显然是错误的)。你会注意到我已经在这个答案的其他地方使用了正确的语法。 - Charles Duffy
是的,在手册中找到了。它说对于 &-{variable} 使用变量的值而不是分配变量。 - Barmar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接