Переместить файл, но только если он закрыт

10

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

Является ли эта тестовая команда правильной?

if lsof "/file/name"
then
        # file is open, don't touch it!
else
        if [ 1 -eq $? ]
        then
                # file is closed
                mv /file/name /other/file/name
        else
                # lsof failed for some other reason
        fi
fi

РЕДАКТИРОВАТЬ: файл представляет собой набор данных, и мне нужно подождать, пока он не будет завершен, чтобы переместить его, чтобы другая программа могла действовать с ним. Вот почему мне нужно знать, выполняется ли внешний процесс с файлом.

Петр Ковач
источник
3
Примечание: после открытия файла процессы используют файловые дескрипторы и данные inode для манипулирования им. Изменение пути (т. Е. Перемещение файла) не доставит слишком много хлопот процессу.
Джон У. С. Смит,
2
Есть ли у вас контроль над внешним процессом? Возможно ли для внешнего процесса создать временный файл и переименовать файл, когда он завершит запись в него?
Дженни Д
@JennyD Я провел небольшое расследование, и это оказалось правдой. Мне не нужно lsofвообще, мне просто нужно проверить, если расширение файла не .tmp. Это делает это тривиальным. Тем не менее я рад , что я спросил мой вопрос , так как я узнал немного о lsofи inotifyи прочее.
Петер Ковач
@PeterKovac Я тоже узнал о них больше, прочитав ответы, поэтому я очень рад, что вы спросили об этом.
Дженни Д
@JohnWHSmith - Обычно это так, если перемещать файл в пределах одной и той же файловой системы, если он перемещает файл в новую файловую систему до того, как автор завершит запись в него, он потеряет некоторые данные.
Джонни

Ответы:

11

Со lsofстраницы руководства

Lsof возвращает единицу (1), если обнаружена какая-либо ошибка, включая ошибку при поиске имен команд, имен файлов, интернет-адресов или файлов, имен входа, файлов NFS, PID, PGID или UID, которые было предложено перечислить. Если указана опция -V, lsof будет указывать элементы поиска, которые не удалось отобразить.

Так что это предполагает, что ваше lsof failed for some other reasonпредложение никогда не будет выполнено.

Вы пытались просто переместить файл, пока ваш внешний процесс все еще открыт? Если каталог назначения находится в той же файловой системе, то проблем с этим не должно быть, если только вам не нужен доступ к нему по исходному пути от третьего процесса, поскольку базовый индекс будет оставаться прежним. Иначе я думаю, mvчто все равно не получится.

Если вам действительно нужно дождаться завершения внешнего процесса с файлом, лучше использовать команду, которая блокирует, а не повторяет опрос. В Linux вы можете использовать inotifywaitдля этого. Например:

 inotifywait -e close_write /path/to/file

Если вы должны использовать lsof(возможно, для переносимости), вы можете попробовать что-то вроде:

until err_str=$(lsof /path/to/file 2>&1 >/dev/null); do
  if [ -n "$err_str" ]; then
    # lsof printed an error string, file may or may not be open
    echo "lsof: $err_str" >&2

    # tricky to decide what to do here, you may want to retry a number of times,
    # but for this example just break
    break
  fi

  # lsof returned 1 but didn't print an error string, assume the file is open
  sleep 1
done

if [ -z "$err_str" ]; then
  # file has been closed, move it
  mv /path/to/file /destination/path
fi

Обновить

Как отмечает @JohnWHSmith ниже, самый безопасный дизайн всегда будет использовать lsofцикл, как указано выше, поскольку возможно, что более одного процесса будет иметь файл, открытый для записи (в качестве примера можно привести плохо записанный демон индексации, который открывает файлы с чтением / Писать флаг, когда он действительно должен быть только для чтения). inotifywaitвсе еще может использоваться вместо сна, просто замените строку сна на inotifywait -e close /path/to/file.

Graeme
источник
Спасибо, я не знал об этом inotify. К сожалению, он не установлен на моей коробке, но я уверен, что где-нибудь найду пакет. См. Мое редактирование по причине, по которой мне нужно закрыть файл: это набор данных, и он должен быть завершен перед дальнейшей обработкой.
Петр Ковач
1
Еще одно замечание: хотя inotifywaitскрипт не позволит «опрашивать» два часто, lsofоператору все равно нужно проверять цикл: если файл открывается дважды, однократное закрытие может вызвать inotifyсобытие, даже если файл не готов к манипулировать (например, в вашем последнем фрагменте кода ваш sleepвызов может быть заменен на inotifywait).
Джон У. С. Смит,
@John a close_writeдолжно быть в порядке, поскольку только один процесс может одновременно открывать файл для записи. Предполагается, что другой не откроет его сразу после закрытия, но такая же проблема существует при lsofопросе.
Грэм,
1
@Graeme Несмотря на то, что в случае с OP это может быть правдоподобно, ядро ​​разрешает дважды открывать файл для записи (в этом случае CLOSE_WRITEзапускается дважды).
Джон У. С. Смит,
@ Джон, обновлено.
Грэм,
4

В качестве альтернативного подхода, это идеальный случай для канала - второй процесс будет обрабатывать выходные данные первого процесса, как только он станет доступным, а не ждать завершения всего процесса:

process1 input_file.dat | process2 > output_file.dat

Преимущества:

  • Гораздо быстрее в целом:
    • Не нужно записывать и читать с диска (этого можно избежать, если вы используете виртуальный диск).
    • Следует более полно использовать ресурсы машины.
  • Нет промежуточного файла для удаления после окончания.
  • Не требуется сложной блокировки, как в OP.

Если у вас нет возможности напрямую создать канал, но у вас есть GNU coreutils, вы можете использовать это:

tail -F -n +0 input_file.dat | process2 > output_file.dat

Это начнёт чтение входного файла с самого начала, независимо от того, как далеко продвинется первый процесс через запись файла (даже если он еще не запущен или уже закончен).

l0b0
источник
Да, это было бы "очевидным" решением. К сожалению, процесс генерации данных находится вне моего контроля (запускается другим пользователем).
Петер Ковач
@PeterKovac Это не имеет значения: cat input_file.dat | process2 output_file.dat
MariusMatutiae
@MariusMatutiae, но catи process2может закончить, прежде чем process1закончится. Они не будут блокировать.
cpugeniusmv