使用bash监视目录中的现有文件和新文件

时间:2018-11-22 08:56:30

标签: bash unix inotify inotifywait

我有一个使用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

我尝试结合两个循环。首先运行第二个循环。但是,如果文件快速大量到达,则将发生变化,即第二个循环正在运行时文件将到达。然后,两个循环都不会捕获这些文件。

鉴于文件已经存在于一个文件夹中,并且新文件将快速到达该文件夹内,那么如何确保脚本可以捕获所有文件?

2 个答案:

答案 0 :(得分:1)

  

使用inotifywait,只能监视新文件。

我要定义“新文件”。 man inotifywait指定事件列表,该列表还列出createdeletedelete_self之类的事件,并且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

其中lockunlock是在脚本和生成部分之间实现的一些锁定机制。您可以在文件创建过程和文件处理过程之间建立通信。

我认为您可以使用某些事务文件系统,该文件系统使您可以从其他脚本“锁定”目录,直到您准备好进行处理为止,但是我在该领域没有经验。

  

我尝试结合两个循环。但是,如果文件大量快速到达,则将发生变化,直到第二个循环正在运行。

在运行process_old_files_loop之前,先在后台运行process_new_file_loop。同样,最好确保(即同步)inotifywait已成功启动,然后再继续进行process-existing-files-loop,以便它们之间也没有竞争条件。

也许一个简单的例子和​​/或起点是:

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并不快,它是一个外壳程序,应该用于互连两个进程(将一个进程的stdout传递到另一个进程的stdin),并且逐行while IFS= read -r line解析流在bash中非常慢,通常应用作不得已。也许像xargsxargs -P0 -n1 sh -c "work on $1; mv $1 $path" --这样的parallel可以加快处理速度,但普通的python或C程序可能快n倍。

答案 1 :(得分: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可能会列出两次 。但是您可以通过使用while来确保在操作之前文件仍然存在,从而轻松地在if [[ -e "$file" ]]循环中处理该问题。
  • 我有点怀疑您的inotifywait选项确实是您想要的;尤其是modify似乎是错误的事件。但我相信您可以根据需要进行调整。除了切换为清晰/明确的长选项并添加--以增强健壮性之外,我上面所做的唯一更改是添加了--format %f,这样您就可以得到没有多余细节的文件名。
  • 除了换行符外,似乎没有任何其他方法告诉inotifywait使用分隔符,因此,我只是顺其自然。确保避免使用包含换行符的文件名。