如果其他进程也可以在此脚本看到文件之前读取并删除该文件,则说明您的系统设计有竞争条件。 (我假设“负责压制”的意思是“旨在取消链接”......)
如果此脚本可以选择查看每个输入文件,则只需将 stderr 重定向到 /dev/null(即在竞争条件出现时忽略错误)。如果它不是可选的,则让这个脚本将输入文件重命名为其他名称,并让其他进程监视 that。在重命名之前检查该文件是否存在,以确保不会覆盖其他进程尚未读取的文件。
你的循环设计很糟糕。首先,您正忙于等待文件的生成(根本没有sleep)。其次,当输入存在时,您正在运行 4 个程序,而不是 1 个。
可以通过使用inotifywait 监视目录的更改来避免忙等待。因此if [[ -f $INPUT_FILE ]] 循环体仅在修改目录后运行,而不是 CPU 内核运行它的速度。
第二个更容易解决:永远不要cat file | something。 something file 或 something < file 如果 something 不在其命令行上使用文件名,或者行为不同。 cat 仅在您要连接多个文件时才有用。要将文件读入 shell 变量,请使用 foo=$(<file)。
我从 cmets 看到您已经设法将整个管道变成一个命令。所以写
INPUT_FILE=foo;
inotifywait -m -e close_write -e moved_to --format %f . |
while IFS= read -r event_file;do
[[ $event_file == $INPUT_FILE ]] &&
awk -F '[<,>]' '/data/ {printf "%s ",$3} END {print ""}' "$INPUT_FILE" 2>/dev/null
# echo "$event_file" &&
# date;
done
# tested and working with the commented-out echo/date commands
请注意,我正在等待close_write 和moved_to,而不是其他事件,以避免跳枪并读取未完成写入的文件。将$INPUT_FILE 放在它自己的目录中,这样您就不会因为其他文件名而唤醒您的循环。
要同时实现 rename-to-input-for-next-stage 建议,您可以在 awk 之后放置一个 while [[ -e $INPUT2 ]]; do sleep 0.2; done; mv -n "$INPUT_FILE" "$INPUT2"busy-wait 循环。
另一种方法是在每次循环迭代时运行一次inotifywait,但这有可能让您陷入在inotifywait 开始观看之前创建的$INPUT_FILE。因此生产者会等待消费者消费,而消费者不会看到该事件。
# Race condition with an asynchronous producer, DON'T USE
while inotifywait -qq -e close_write -e moved_to; do
[[ $event_file == $INPUT_FILE ]] &&
awk -F '[<,>]' '/data/ {printf "%s ",$3} END {print ""}' "$INPUT_FILE" 2>/dev/null
done
似乎没有办法指定尚不存在的文件的名称,即使作为过滤器也是如此,因此循环体需要在使用前测试目录中存在的特定文件。
如果您没有可用的 inotifywait,您可以将 sleep 放入循环中。 GNU sleep 支持小数秒,例如sleep 0.5。 Busybox 可能不会。无论如何,您可能想编写一个很小的琐碎 C 程序,它不断尝试在包含usleep 或nanosleep 的循环中open(2) 文件。当open 成功时,从那里重定向标准输入,然后exec 你的awk 程序。这样一来,stat 和 open 之间就不会存在竞争。
#include <unistd.h> // for usleep/dup2
#include <sys/types.h> // for open
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h> // for perror
void waitloop(const char *path)
{
const char *const awk_args[] = { "-F", "[<,>]",
"/data/ {printf \"%s \",$3} END {print \"\"}",
path
};
while(42) {
int fd = open(path, O_RDONLY);
if (-1 != fd) {
// if you fork() here, you can avoid the shell loop too.
dup2(fd, 0); // redirect stdin from fd. In theory should check for error here, too.
close(fd); // and do this in the parent after fork
execv("/usr/bin/awk", (char * const*)awk_args); // execv's prototype doesn't prevent it from modifying the strings?
} else if(errno != ENOENT) {
perror("opening the file");
} // else ignore ENOENT
usleep(10000); // 10 milliseconds.
}
}
// optional TODO: error-check *all* the system calls.
这可以编译,但我还没有测试过。在单个进程中循环执行 open / usleep 比在 shell 中运行整个进程执行 sleep 0.01 轻得多。
更好的是使用 inotify 来监视目录事件以检测出现的文件,而不是 usleep。为避免争用,在设置 inotify watch 后,再次检查文件是否存在,以防它是在您上次检查之后但在 inotify watch 激活之前创建的。