使用 bash 监视目录中的现有文件和新文件
Posted
技术标签:
【中文标题】使用 bash 监视目录中的现有文件和新文件【英文标题】:Monitor Pre-existing and new files in a directory with bash 【发布时间】:2018-11-22 08:56:29 【问题描述】:我有一个使用 inotify-tool
的脚本。
此脚本会在新文件到达文件夹时发出通知。它对文件执行一些工作,完成后将文件移动到另一个文件夹。 (看起来是这样的):
inotifywait -m -e modify "$path" |
while read NEWFILE
work on/with NEWFILE
move NEWFILE no a new directory
done
使用inotifywait
,只能监控新文件。使用for OLDFILE in path
而不是inotifywait
的类似过程将适用于现有文件:
for OLDFILE in $path
do
work on/with OLDFILE
move NEWFILE no a new directory
done
我尝试将这两个循环结合起来。通过首先运行第二个循环。但是,如果文件快速到达并且数量很大,那么文件将在第二个循环运行时到达的变化。然后,两个循环都不会捕获这些文件。
鉴于文件夹中已经存在文件,并且新文件会很快到达文件夹中,如何确保脚本能够捕获所有文件?
【问题讨论】:
在运行 inotifyywait 脚本之前将“旧”文件移出? @redCricket 我认为这就是我正在做的事情。问题是文件到达太快了,所以假设文件夹中已经有 X 个文件,那么在移动这些文件时,Y 个文件到达。然后 inotifywait 不会检测到 Y 文件 把整个目录吹走,然后重新创建。 【参考方案1】:一旦inotifywait
启动并等待,它会将消息Watches established.
打印到标准错误。因此,您需要在之后查看现有文件。
因此,一种方法是编写将处理标准错误的内容,并在看到该消息时列出所有现有文件。为方便起见,您可以将该功能包装在一个函数中:
function list-existing-and-follow-modify()
local path="$1"
inotifywait --monitor \
--event modify \
--format %f \
-- \
"$path" \
2> >( while IFS= read -r line ; do
printf '%s\n' "$line" >&2
if [[ "$line" = 'Watches established.' ]] ; then
for file in "$path"/* ; do
if [[ -e "$file" ]] ; then
basename "$file"
fi
done
break
fi
done
cat >&2
)
然后写:
list-existing-and-follow-modify "$path" \
| while IFS= read -r file
# ... work on/with "$file"
# move "$file" to a new directory
done
注意事项:
如果您不熟悉我使用的>(...)
表示法,它被称为“进程替换”;详情请见https://www.gnu.org/software/bash/manual/bash.html#Process-Substitution。
上面的竞争条件现在与原来的竞争条件相反:如果在inotifywait
启动后不久创建了一个文件,那么list-existing-and-follow-modify
可能会列出它两次。但是您可以通过使用if [[ -e "$file" ]]
在您的while
-loop 中轻松处理该问题,以确保在您对其进行操作之前该文件仍然存在。
我有点怀疑你的inotifywait
选项真的是你想要的;尤其是modify
,似乎是错误的事件。但我相信你可以根据需要调整它们。除了为了清晰/明确切换到长选项并添加 --
以确保稳健性之外,我在上面所做的唯一更改是添加 --format %f
以便您获得没有无关细节的文件名。
似乎没有任何方法可以告诉inotifywait
使用换行符以外的分隔符,所以,我只是顺其自然。确保避免使用包含换行符的文件名。
【讨论】:
嗨。谢谢你。处理标准错误似乎有效。我看不到cat
的意义。但是,列出标准错误,然后遍历现有文件似乎可行。
@J.doe:cat
的重点是继续将inotifywait
的标准错误转发到list-existing-and-follow-modify
的标准错误,即使我们已经处理了Watches established.
消息。 (否则,任何后续的错误或警告消息都将被静默丢弃。)
我测试了你的代码,确实解决了问题。但是,运行每个进程,从同一个文件夹中读取,会使每个进程读取和处理相同的文件。但你的回答确实回答了我最初的问题。【参考方案2】:
通过使用 inotifywait,只能监控新文件。
我会询问“新文件”的定义。 man inotifywait 指定了一个事件列表,其中还列出了诸如create
和delete
和delete_self
之类的事件,并且 inotifywait 还可以监视“旧文件”(被定义为在 inotifywait 执行之前存在的文件)和目录。您只指定了一个事件 -e modify
,它通知 $path 中文件的修改,它包括对两个预先存在的文件的修改,并在 inotify 执行后创建。
...如何确保脚本能够捕获所有文件?
您的脚本足以捕捉路径内发生的所有事件。如果您无法在生成文件的部分和接收文件的部分之间进行同步,那么您将无能为力,并且总是会出现竞争条件。如果您的脚本收到 0% 的 CPU 时间,而生成文件的部分将获得 100% 的 CPU 时间怎么办?不能保证进程之间的 cpu 时间(除非使用经过认证的实时系统......)。在它们之间实现同步。
您可以观看其他活动。如果生成站点在准备好文件时关闭文件,请注意关闭事件。您也可以在后台并行运行work on/with NEWFILE
以加快执行和读取新文件的速度。但是如果接收端比发送端慢,如果你的脚本在 NEWFILEs 上运行的速度比生成新文件的速度慢,你就无能为力了......
如果文件名中没有特殊字符和空格,我会选择:
inotifywait -m -e modify "$path" |
while IFS=' ' read -r path event file ;do
lock "$path"
work on "$path/$file"
ex. mv "$path/$file" $new_location
unlock "$path"
done
其中lock
和unlock
是在您的脚本和生成部分之间实现的一些锁定机制。您可以在文件创建进程和文件处理进程之间创建通信。
我认为您可以使用一些事务文件系统,它可以让您从其他脚本“锁定”一个目录,直到您准备好对其进行工作,但我在该领域没有经验。
我尝试将这两个循环结合起来。但是,如果文件快速到达并且数量很大,那么文件将在第二个循环运行时到达的变化。
在运行 process_old_files_loop 之前在后台运行 process_new_file_loop。在继续处理现有文件循环之前,最好确保(即同步)inotifywait 已成功启动,这样它们之间也不会出现竞争条件。
也许一个简单的例子和/或起点是:
work()
local file="$1"
some work "$file"
mv "$file" "$predefiend_path"
process_new_files_loop()
# let's work on modified files in parallel, so that it is faster
trap 'wait' INT
inotifywait -m -e modify "$path" |
while IFS=' ' read -r path event file ;do
work "$path/$file" &
done
process_old_files_loop()
# maybe we should parse in parallel here too?
# maybe export -f work; find "$path -type f | xargs -P0 -n1 -- bash -c 'work $1' -- ?
find "$path" -type f |
while IFS= read -r file; do
work "$file"
done
process_new_files_loop &
child=$!
sleep 1
if ! ps -p "$child" >/dev/null 2>&1; then
echo "ERROR running processing-new-file-loop" >&2
exit 1
fi
process_old_files_loop
wait # wait for process_new_file_loop
如果您真的关心执行速度并希望更快地完成,请更改为 python 或 C(或除 shell 之外的任何内容)。 bash 并不快,它是一个 shell,应该用于互连两个进程(将一个的 stdout 传递给另一个的 stdin)并逐行解析流while IFS= read -r line
在 bash 中非常慢,通常应该用作最后采取。也许使用xargs
像xargs -P0 -n1 sh -c "work on $1; mv $1 $path" --
或parallel
这样可以加快速度,但普通的python 或C 程序可能会快n 倍。
【讨论】:
【参考方案3】:更简单的解决方案是在子shell 中的inotifywait 前面添加一个ls,并使用awk 创建看起来像inotifywait 的输出。
我用它来检测和处理现有的和新的文件:
(ls $path | awk 'print "'$path' EXISTS "$1' && inotifywait -m $path -e close_write -e moved_to) |
while read dir action file; do
echo $action $dir $file
# DO MY PROCESSING
done
所以它运行 ls,格式化输出并将其发送到 stdout,然后在同一个子 shell 中运行 inotifywait,将输出也发送到 stdout 进行处理。
【讨论】:
以上是关于使用 bash 监视目录中的现有文件和新文件的主要内容,如果未能解决你的问题,请参考以下文章
如何在不使用 inotifywait 的情况下监视目录的文件更改?