我尝试将两个独立脚本生成的两个变量的值写入单个“file.cfg”中。这两个变量不断更新并保存在“file.cfg”中。下面是我的工作示例。
例如“file.cfg”内容:
a=null
b=null
例如“script_a.sh”使用以下命令更新“a”的值:
#!/bin/bash
while : do
.............
val_a=1
sed -i "s/^\(a=\).*/\1$val_a/" file.cfg
.............
done
例如“script_b.sh”使用以下命令更新“b”的值:
#!/bin/bash
while : do
.............
val_b=2
sed -i "s/^\(b=\).*/\1$val_b/" file.cfg
.............
done
脚本运行正常,值也更新了。但如果同时执行这两个脚本,其中一个值不会更新。
我发现带有“-i”选项的 sed 会创建一个临时文件,该文件会被两个同时执行的操作覆盖。我该如何解决?
答案1
其他答案利用 lockfile 的思想。还有另一个实用程序:flock(1)
。从它的手册:
flock [options] file|directory command [arguments]
flock [options] file|directory -c command
[…]该实用程序
flock(2)
从 shell 脚本或命令行管理锁。
su(1)
上述第一种和第二种形式以类似于或 的方式将锁包裹在命令执行周围newgrp(1)
。它们锁定指定的文件或目录,如果该文件或目录尚不存在,则创建该文件或目录(假设具有适当的权限)。默认情况下,如果无法立即获取锁,则flock
等待直到锁可用。
而且因为它使用flock(2)
系统调用,我相信内核保证没有两个进程可以持有同一个文件的锁:
LOCK_EX
放置独占锁。在给定时间内,只有一个进程可以持有给定文件的独占锁。
在您的脚本中,不要sed …
运行flock some_lockfile sed …
,例如
flock some_lockfile sed -i "s/^\(a=\).*/\1$val_a/" file.cfg
就是这样,sed
退出时锁会被释放。唯一的缺点是:
some_lockfile
可能已被用作锁文件;安全的方法是mktemp
创建一个临时文件并使用它;- 最后你需要删除它
some_lockfile
(我猜你不想把它当作垃圾留下);但如果有其他东西使用该文件(可能不是作为锁文件),你可能不想删除它;同样,mktemp
要走的路是:创建一个临时文件,使用它,删除它——不管其他进程做什么。
那为什么不呢flock file.cfg sed …
?它会锁定正在操作的确切文件;这样就不会留下垃圾。为什么不呢?
嗯,因为这是有缺陷的。为了理解它,让我们看看(GNU)sed -i
确实如此:
-i[SUFFIX]
--in-place[=SUFFIX]
此选项指定文件将就地编辑。GNU
sed
通过创建临时文件并将输出发送到此文件而不是标准输出来实现此目的。[…]
当到达文件末尾时,临时文件将重命名为输出文件的原始名称。如果提供了扩展名,则在重命名临时文件之前使用该扩展名修改旧文件的名称,从而制作备份副本。
我已经测试过flock
锁定 inode 而不是名称(路径)。这意味着在sed -i
将临时文件重命名为原始名称(file.cfg
在您的例子中)后,锁定不再适用于原始名称。
现在考虑以下场景:
- 第一个
flock file.cfg sed -i … file.cfg
锁定原始文件并对其进行处理。 - 在第一个任务
sed
完成之前,另一个flock file.cfg sed -i … file.cfg
任务出现。这个新flock
任务以原始任务为目标file.cfg
,等待第一个锁被释放。 - 第一个进程
sed
将其临时文件移至原始名称并退出。第一个锁被释放。 - 第二个进程
flock
生成第二个进程,第二个sed
进程现在打开新的file.cfg
。此文件不是原始文件(因为 inode 不同)。但第二个进程flock
定位并锁定了原始文件,而不是第二个进程sed
刚刚打开的文件! - 在第二个任务
sed
完成之前,另一个flock file.cfg sed -i … file.cfg
任务出现了。这个新任务flock
检查当前的任务file.cfg
,发现它的没有锁定;它锁定文件并生成sed
。第三个sed
开始读取当前的file.cfg
。 - 现在有两个
sed -i
进程并行读取同一个文件。无论哪个进程先结束,都会失败——另一个进程最终会通过将其独立副本移至原始名称来覆盖结果。
这就是为什么您需要some_lockfile
一个坚如磐石的 inode 编号。
答案2
锁文件应该运行良好,如果锁文件存在,则某些进程正在使用目标文件并且其他进程将不得不等待。
如果您已经获得该lockfile-progs
包,那么您可以使用它来检查是否存在有效的锁(在过去 5 分钟内)lockfile-check
以及类似的lockfile-create
& lockfile-remove
。
请注意,这些锁文件不锁定或阻止对文件的访问,但只是提供信息,以便你的脚本知道不要互相干扰。
lockfile-create
如果锁定文件已经存在,则默认延迟,它会等到文件解锁后再继续。以下是其手册页中的一段摘录:
-r retry-count, --retry retry-count
在放弃之前尝试锁定文件名重试次数。每次尝试都会比上一次延迟一段时间(以 5 秒为增量),直到重试之间的最大延迟时间为一分钟。如果未指定重试次数,则默认值为 9,如果所有 9 次锁定尝试都失败,则将在 180 秒(3 分钟)后放弃。
这是一个基本示例,允许在 file.cfg 被锁定时执行多个命令(包括失败时退出lockfile-create
),但请参阅手册页以了解更多详细信息。:
lockfile-create file.cfg || { echo "lockfile-create failed, exiting now"; exit; }
...
sed -i ... file.cfg
...
lockfile-remove file.cfg
如果您需要锁定文件超过 5 分钟,请使用lockfile-touch
“永远运行,每分钟触摸一次锁,直到被杀死。”以下是手册页的摘录:
Locking a file during a lengthy process:
lockfile-create /some/file
lockfile-touch /some/file &
# Save the PID of the lockfile-touch process
BADGER="$!"
do-something-important-with /some/file
kill "${BADGER}"
lockfile-remove /some/file
如果你确实想在等待文件解锁时做一些特别的事情,你可以使用像这样的 while 循环,但time
在检查和锁定文件之间可能会有几毫秒的窗口(在我的测试中为 0.003 秒),但 lockfile-create 无论如何都会等到可以安全继续进行
while lockfile-check file.cfg
do
echo doing stuff waiting for lock to clear
sleep 1
done
lockfile-create file.cfg || exit
...
sed -i ... file.cfg
...
lockfile-remove file.cfg
只要两个脚本都使用并尊重锁文件,sed
就永远不可能在文件解锁时替换它,因此就不应该有文件复制和重命名冲突。
或者还有其他类似的选择,例如:
dotlockfile
- 你自己
test -a FILE
&touch
... flock
如同卡米尔的回答包装coreutils
很好- 将值存储在可以安全处理同时访问的数据库程序中
答案3
-i[后缀], --in-place[=后缀]
就地编辑文件(如果提供了扩展,则进行备份)。默认操作模式是断开符号和硬链接。可以使用 --follow-symlinks 和 --copy 进行更改。
-c,--复制
在 -i 模式下对文件进行改组时,请使用 copy 而不是 rename。虽然这可以避免破坏链接(符号或硬链接),但由此产生的编辑操作不是原子的。这很少是理想的模式;--follow-symlinks 通常就足够了,而且它既快又安全。
每当您设置了某些别名或命令时,请看一下它到底是什么样子。根据手册页,如果您只是使用,则不应该创建备份-i
。
这并不意味着两者不能同时访问文件并覆盖彼此的更改。在这种情况下,使用互斥锁或类似方法可能是明智的。
答案4
这是典型的实时更新问题:script_a.sh
读取file.cfg
,在写入任何更改之前,script_b.sh
读取相同的信息;然后,无论哪个脚本先写入其更新,当另一个脚本发布其更新时,其更改将被覆盖。更新是通过临时文件完成的还是通过直接写入完成的,都无关紧要。
其中没有原生的信号量或互斥处理bash
,但你可以file.cfg
通过在脚本中添加行来使用它本身,例如script_a.sh
:-
#!/bin/bash
while : do
.............
while ! mv file.cfg file.cfg_a 2>/dev/nul; do sleep 0.1; done
val_a=1
sed -i "s/^\(a=\).*/\1$val_a/" file.cfg_a
mv file.cfg_a file.cfg
.............
done
的更改script_b.sh
类似,只是文件被重命名为file.cfg_b
以便进行更新。
通过使用重命名命令,脚本既可以检查文件是否可用以更新,又可以在单一、不间断的过程中获得唯一的访问权限。
我从不喜欢轮询循环,但是在没有编译支持处理信号量和互斥量的函数的代码的情况下,这是可以轻松完成的最好的事情。
请注意,某些版本sleep
不支持小数延迟,在这种情况下,您需要延迟至少一秒钟才能重试,除非您使用其他实用程序。