使用 bash 监视目录中的现有文件和新文件
Monitor Pre-existing and new files in a directory with bash
我有一个使用 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
我尝试结合这两个循环。通过第一个运行第二个循环。但是,如果文件快速到达且数量众多,则会发生变化,即文件将在第二个循环 运行 时到达。这些文件将不会被两个循环捕获。
鉴于文件已存在于文件夹中,并且新文件将很快到达文件夹中,如何确保脚本将捕获所有文件?
By using inotifywait, one can only monitor new files.
我会要求 "new file" 的定义。 man inotifywait specifies a list of events, which also lists events like create
and delete
and delete_self
and inotifywait can also watch "old files" (beeing defined as files existing prior to inotifywait execution) and directories. You specified only a single event -e modify
which notifies about modification of files within ${path}, it includes modification of both preexisting files and created after inotify执行。
... how can one make sure that the script will catch all files?
您的脚本足以捕获路径内发生的所有事件。如果您无法在生成文件的部分和接收文件的部分之间进行同步,那么您将无能为力,并且 总是 成为竞争条件。如果您的脚本接收 0% 的 CPU 时间,而生成文件的部分将获得 100% 的 CPU 时间怎么办?无法保证进程之间的 cpu 时间(除非使用经过认证的实时系统...)。在它们之间实现同步。
您可以观看其他活动。如果生成站点在准备好文件时关闭文件,请注意关闭事件。你也可以在后台并行 运行 work on/with NEWFILE
来加速执行和读取新文件。但是,如果接收端比发送端慢,如果您的脚本处理 NEWFILE 的速度比生成新文件部分慢,那么您将无能为力...
如果文件名中没有特殊字符和空格,我会选择:
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
是在您的脚本和生成部分之间实现的一些锁定机制。您可以在文件创建过程和文件处理过程之间创建通信。
我认为您可以使用一些事务文件系统,它可以让您从其他脚本 "lock" 一个目录,直到您准备好处理它,但我在该领域没有经验。
I tried combining the two loops. But if files arrive quickly and in large numbers there is a change that the files will arrive wile the second loop is running.
运行 process_new_file_loop 在 运行 宁 process_old_files_loop 之前在背景中。在继续处理现有文件循环之前确保(即同步)inotifywait 已成功启动也很好,这样它们之间也没有竞争条件。
也许一个简单的例子 and/or 起点是:
work() {
local file=""
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 ' -- ?
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,应该用于互连两个进程(将一个进程的标准输出传递给另一个进程的标准输入)并且逐行解析流 while IFS= read -r line
非常慢在 bash 中,通常应作为最后的手段使用。也许像 xargs -P0 -n1 sh -c "work on ; mv $path" --
或 parallel
这样使用 xargs
会加快速度,但平均 python 或 C 程序可能会快 n 倍。
一旦 inotifywait
启动并等待,它会将消息 Watches established.
打印到标准错误。所以你需要在 之后 遍历现有文件。
因此,一种方法是编写一些程序来处理标准错误,当它看到该消息时,列出所有现有文件。为了方便起见,您可以将该功能包装在一个函数中:
function list-existing-and-follow-modify() {
local path=""
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
备注:
- 如果您不熟悉我使用的
>(...)
表示法,它叫做 "process substitution";有关详细信息,请参阅 https://www.gnu.org/software/bash/manual/bash.html#Process-Substitution。
- 上面的竞争条件现在与原来的竞争条件相反:如果文件是在
inotifywait
启动后不久创建的,那么 list-existing-and-follow-modify
可能会列出它 两次。但是您可以在 while
循环中轻松处理它,方法是使用 if [[ -e "$file" ]]
确保文件在您对其进行操作之前仍然存在。
- 我有点怀疑您的
inotifywait
选项是否真的是您想要的; modify
,特别是,似乎是错误的事件。但我相信你可以根据需要调整它们。我在上面所做的唯一更改,除了切换到 clarity/explicitly 的长选项并添加 --
以提高稳健性之外,就是添加 --format %f
以便您获得没有无关细节的文件名。
- 似乎没有任何方法可以告诉
inotifywait
使用除换行符以外的分隔符,所以,我只是顺其自然。确保避免包含换行符的文件名。
一个更简单的解决方案是在子 shell 中的 inotifywait 前面添加一个 ls,用 awk 创建看起来像 inotifywait 的输出。
我用它来检测和处理现有文件和新文件:
(ls ${path} | awk '{print "'${path}' EXISTS "}' && 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 进行处理。
我有一个使用 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
我尝试结合这两个循环。通过第一个运行第二个循环。但是,如果文件快速到达且数量众多,则会发生变化,即文件将在第二个循环 运行 时到达。这些文件将不会被两个循环捕获。
鉴于文件已存在于文件夹中,并且新文件将很快到达文件夹中,如何确保脚本将捕获所有文件?
By using inotifywait, one can only monitor new files.
我会要求 "new file" 的定义。 man inotifywait specifies a list of events, which also lists events like create
and delete
and delete_self
and inotifywait can also watch "old files" (beeing defined as files existing prior to inotifywait execution) and directories. You specified only a single event -e modify
which notifies about modification of files within ${path}, it includes modification of both preexisting files and created after inotify执行。
... how can one make sure that the script will catch all files?
您的脚本足以捕获路径内发生的所有事件。如果您无法在生成文件的部分和接收文件的部分之间进行同步,那么您将无能为力,并且 总是 成为竞争条件。如果您的脚本接收 0% 的 CPU 时间,而生成文件的部分将获得 100% 的 CPU 时间怎么办?无法保证进程之间的 cpu 时间(除非使用经过认证的实时系统...)。在它们之间实现同步。
您可以观看其他活动。如果生成站点在准备好文件时关闭文件,请注意关闭事件。你也可以在后台并行 运行 work on/with NEWFILE
来加速执行和读取新文件。但是,如果接收端比发送端慢,如果您的脚本处理 NEWFILE 的速度比生成新文件部分慢,那么您将无能为力...
如果文件名中没有特殊字符和空格,我会选择:
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
是在您的脚本和生成部分之间实现的一些锁定机制。您可以在文件创建过程和文件处理过程之间创建通信。
我认为您可以使用一些事务文件系统,它可以让您从其他脚本 "lock" 一个目录,直到您准备好处理它,但我在该领域没有经验。
I tried combining the two loops. But if files arrive quickly and in large numbers there is a change that the files will arrive wile the second loop is running.
运行 process_new_file_loop 在 运行 宁 process_old_files_loop 之前在背景中。在继续处理现有文件循环之前确保(即同步)inotifywait 已成功启动也很好,这样它们之间也没有竞争条件。
也许一个简单的例子 and/or 起点是:
work() {
local file=""
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 ' -- ?
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,应该用于互连两个进程(将一个进程的标准输出传递给另一个进程的标准输入)并且逐行解析流 while IFS= read -r line
非常慢在 bash 中,通常应作为最后的手段使用。也许像 xargs -P0 -n1 sh -c "work on ; mv $path" --
或 parallel
这样使用 xargs
会加快速度,但平均 python 或 C 程序可能会快 n 倍。
一旦 inotifywait
启动并等待,它会将消息 Watches established.
打印到标准错误。所以你需要在 之后 遍历现有文件。
因此,一种方法是编写一些程序来处理标准错误,当它看到该消息时,列出所有现有文件。为了方便起见,您可以将该功能包装在一个函数中:
function list-existing-and-follow-modify() {
local path=""
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
备注:
- 如果您不熟悉我使用的
>(...)
表示法,它叫做 "process substitution";有关详细信息,请参阅 https://www.gnu.org/software/bash/manual/bash.html#Process-Substitution。 - 上面的竞争条件现在与原来的竞争条件相反:如果文件是在
inotifywait
启动后不久创建的,那么list-existing-and-follow-modify
可能会列出它 两次。但是您可以在while
循环中轻松处理它,方法是使用if [[ -e "$file" ]]
确保文件在您对其进行操作之前仍然存在。 - 我有点怀疑您的
inotifywait
选项是否真的是您想要的;modify
,特别是,似乎是错误的事件。但我相信你可以根据需要调整它们。我在上面所做的唯一更改,除了切换到 clarity/explicitly 的长选项并添加--
以提高稳健性之外,就是添加--format %f
以便您获得没有无关细节的文件名。 - 似乎没有任何方法可以告诉
inotifywait
使用除换行符以外的分隔符,所以,我只是顺其自然。确保避免包含换行符的文件名。
一个更简单的解决方案是在子 shell 中的 inotifywait 前面添加一个 ls,用 awk 创建看起来像 inotifywait 的输出。
我用它来检测和处理现有文件和新文件:
(ls ${path} | awk '{print "'${path}' EXISTS "}' && 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 进行处理。