Мониторинг уже существующих и новых файлов в каталоге с bash - PullRequest
0 голосов
/ 22 ноября 2018

У меня есть скрипт, использующий 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 голосов
/ 22 ноября 2018

Как только 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 не может сказать, что нужно использовать разделитель, кроме символов новой строки, поэтому я просто покончил с этим.Обязательно избегайте имен файлов, включающих переводы строк.
0 голосов
/ 22 ноября 2018

Используя inotifywait, можно отслеживать только новые файлы.

Я бы попросил дать определение "нового файла". man inotifywait определяет список событий, в котором также перечислены события, такие как create и delete и delete_self, а inotifywait также может просматривать «старые файлы» (определяемые как файлы, существующие до выполнения inotifywait) и каталоги.Вы указали только одно событие -e modify, которое уведомляет об изменении файлов в $ {path}, оно включает в себя изменение как ранее существующих файлов, так и созданных после inotify выполнения.

.... как можно убедиться, что скрипт будет перехватывать все файлы?

Твоего скрипта достаточно, чтобы перехватить все события, которые происходят внутри пути.Если у вас нет средств синхронизации между частью, которая генерирует файлы, и частью, которая получает, вы ничего не можете сделать, и всегда будет состоянием гонки.Что если ваш сценарий получит 0% процессорного времени, а часть, которая генерирует файлы, получит 100% процессорного времени?Нет никакой гарантии, что процессорное время будет между процессами (если только не используется сертифицированная система реального времени ...).Реализуйте синхронизацию между ними.

Вы можете посмотреть другое событие.Если генерирующие сайты закрывают файлы, когда готовы с ними, следить за событием закрытия.Также вы можете запустить 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 - это некоторые механизмы блокировки, реализованные между вашим сценарием и генерирующей частью.Вы можете создать связь между процессом создания файлов и процессом обработки файлов.

Я думаю, что вы можете использовать некоторую файловую систему транзакций, которая позволит вам "заблокируйте каталог из других сценариев, пока вы не будете готовы к работе над ним, но у меня нет опыта в этой области.

Я попытался объединить два цикла.Но если файлы приходят быстро и в больших количествах, происходит изменение в том, что файлы будут поступать, когда будет запущен второй цикл.

Запустите process_new_file_loop в фоновом режиме перед запуском process_old_files_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 не быстрый, это оболочка, он должен использоваться для соединения двух процессов (передачи стандартного вывода одного в стандартный поток другого), а синтаксический анализ строки потока за строкой while IFS= read -r line чрезвычайно медленный в bash и обычно должен использоваться как последнийкурорт.Возможно, использование xargs, как xargs -P0 -n1 sh -c "work on $1; mv $1 $path" -- или parallel, будет означать ускорение, но средняя программа на Python или C, вероятно, будет в n-й раз быстрее.

...