前言

偶然看到一块使用flock命令的shell,由于学识尚浅,第一眼没有理解其中结构,所以诞生这篇文章,先贴代码:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

lockfile=".lock"
exec 200<${lockfile}
# 其他n行内容
flock -w 2 200
if [ $? -eq 0 ]; then
echo "lock success"
else
echo "lock failed"
fi

一开始没有看到上面的exec命令,只看到两个脚本里分别写了flock -w 2 200,就在想这个200到底是什么
并且自己尝试在命令行里执行就会报错flock: 200: Bad file descriptor
然后当时以为是存在一个大家都可以访问的并且值为200的文件描述符,但这不可能啊,文件描述符fd是进程隔离,应该不会有这种情况!
之后带着疑惑查看了整个脚本,发现两个脚本中都有exec命令带有200的参数,深入学习后,解释一下这个代码:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

lockfile=".lock" # 这就是简单的一个变量
exec 200<${lockfile} # 这里exec作用是IO重定向,将200文件描述符重定向到只读lockfile文件。可以直观理解为只读方式打开lockfile文件并且设置其文件描述符为200
# 其他n行内容
flock -w 2 200 # flock命令可以直接跟文件描述符进行设置锁
if [ $? -eq 0 ]; then # 判断是否获取锁成功
echo "lock success"
else
echo "lock failed"
fi

那接下来就把依次用到的内容做个扩展整理

整理

注:这里基于Ubuntu24.04平台,标准bash shell

flock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# flock -h
Usage:
flock [options] <file>|<directory> <command> [<argument>...]
flock [options] <file>|<directory> -c <command>
flock [options] <file descriptor number>

Manage file locks from shell scripts.

Options:
-s, --shared get a shared lock
-x, --exclusive get an exclusive lock (default)
-u, --unlock remove a lock
-n, --nonblock fail rather than wait
-w, --timeout <secs> wait for a limited amount of time
-E, --conflict-exit-code <number> exit code after conflict or timeout
-o, --close close file descriptor before running command
-c, --command <command> run a single command string through the shell
-F, --no-fork execute command without forking
--verbose increase verbosity

-h, --help display this help
-V, --version display version

For more details see flock(1).
  • 首先参数的各种含义,没有特殊需要解释的,如果不理解百度即可
  • flock是一个文件锁,重点是文件,哪怕是通过不同的文件描述符,只要文件是相同的,那就是同一个
  • 而且flock是君子锁,非强制锁,哪怕获取锁失败,也不影响对应文件的读写使用

flock的常用方式

除了上面例子里先用exec命令重定向之外,还有一种常用的方式:

1
2
3
4
5
6
7
8
9
lockfile=".lock"
{
flock -w 2 200
if [ $? -eq 0 ]; then
echo "lock success"
else
echo "lock failed"
fi
} 200<${lockfile}

这里通过括号使用了匿名函数,并且将lockfile重定向到200的文件描述符,供匿名函数内使用,到这里有必要说明一下作用域和生命周期

  • 上面通过exec进行重定向的文件描述符,作用域是整个脚本,脚本结束后文件描述符会被释放,整个脚本都可以使用这个文件描述符,生命周期也就是脚本的生命周期
  • 刚刚通过匿名函数的方式打开的文件描述符,作用域是匿名函数内部,生命周期是匿名函数的生命周期,也就是匿名函数执行完成后,文件描述符就会被关闭

exec

首先说明的是,exec属于bash的内建命令,而flock是独立的二进制

1
2
3
4
# which exec 
exec: shell built-in command
# which flock
/usr/bin/flock

替换当前进程

exec 后跟其他命令,可以执行该命令,它和直接执行该命令的区别是:exec执行命令实际是替换命令,这期间不会新创建进程,pid等内容自然不会改变,并且由于当前进程命令已经被改变,所以exec之后的命令不会被执行

1
2
3
echo "now is normal bash"
exec ls # 这里将当前bash脚本内容替换成了执行ls,ls执行后命令结束,后面的动作不会执行,因为后面的动作是bash脚本,而不是ls的内容
echo "this will not be executed"

重定向

文章中和flock命令搭配使用的场景,就是用的exec的重定向功能

网上的资料大家都说是exec的重定向功能,但是我认为重定向功能实际是 < 和 > 操作符完成的,因为它们才是真正的重定向符号
exec在这里起的作用应该只是执行 < 和 > 符号的功能而已

1
2
3
4
5
exec 3<&0		# 将标准输入(文件描述符0)重定向到文件描述符3
exec > file # 将标准输出重定向到文件file,默认是1
exec 1> file # 将标准输出重定向到文件file
exec 10>file # 将文件描述符10重定向到文件file
exec 10<&- # 关闭文件描述符10

注意:
‘<’ 操作符是读取重定向,也是标准输入重定向,后面如果要跟文件,则需要文件存在并且对当前用户有只读权限
‘>’ 和 ‘>>’ 操作符是写重定向,也是标准输出重定向,后面跟文件,文件可以不存在,它会自己创建,这两者的区别在于’>>’ 是追加写
三个操作符如果要跟文件描述符,前后不能有空格
一个进程默认打开三个文件描述符,标准输入0,标准输出1,标准错误2

括号

在shell脚本中,括号存在很多种用法,功能丰富,但是这里只列一种,代码块

1
2
{ ls }
( ls )
  • 以上分别是用大括号和小括号列举的代码块,代码块中执行的均为ls命令,两者均可以理解为匿名函数
  • 代码块要求括号和命令之间必须存在空格
  • 大括号和小括号实现的代码块区别
    • 小括号会另起一个shell进程去执行,而大括号则是当前shell进程执行
    • 大括号里的变量在后续脚本中能继续使用,小括号里的则不行

参考