移动文件,但仅在关闭状态下


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
您可以控制外部流程吗?外部进程是否可以创建一个临时文件并在文件写入完毕后重命名该文件?
珍妮·D

@JennyD我做了一些调查,事实证明这是真的。我根本不需要lsof检查文件扩展名是否不是.tmp。这使它变得微不足道。不过,我很高兴我问我的问题,因为我了解了一些关于lsofinotify和东西。
彼得·科瓦奇

@PeterKovac通过阅读答案,我也了解了更多有关它们的信息,因此,我很高兴您提出要求。
珍妮·D

@JohnWHSmith-如果在同一个文件系统中移动文件,这通常是正确的;如果在编写者完成对文件的写入之前,他将文件移动到新的文件系统,则他将丢失一些数据。
约翰尼

Answers:


11

lsof手册页

如果检测到任何错误,则Lsof将返回一(1),包括未能找到要求列出的命令名称,文件名,Internet地址或文件,登录名,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循环,因为可能有多个进程打开了要写入的文件(示例情况可能是编写不良的索引守护程序,该后台驻留程序打开了具有读取操作的文件/ write标志,则该标志应为只读)。inotifywait仍然可以代替睡眠使用,只需用替换睡眠行inotifywait -e close /path/to/file


谢谢,我没有意识到inotify。不幸的是,它没有安装在我的盒子上,但是我确定我会在某个地方找到一个包装。请参阅我的编辑以了解为什么我需要关闭文件的原因:它是一个数据集,必须完整后才能进一步处理。
彼得·科瓦奇

1
另一个注意事项:虽然inotifywait可以防止脚本经常“轮询”两个脚本,但OP仍需要检查lsof循环:如果文件打开了两次,则关闭一次可能触发inotify事件,即使文件还没有准备好。被操纵的(例如,在您的最后一段代码中,您的sleep调用可以替换为inotifywait)。
约翰·史密斯

@John a close_write应该没问题,因为一次只能打开一个进程以供写入。它确实假设另一个在关闭后不会立即打开它,但是lsof轮询仍然存在相同的问题。
Graeme 2014年

1
@Graeme虽然在设计OP的情况下这可能是正确的,但内核确实允许将文件打开两次以进行写入(在这种情况下,CLOSE_WRITE将触发两次)。
约翰·史密斯

@John,更新。
Graeme 2014年

4

作为一种替代方法,这是管道的理想情况-第二个过程将在第一个过程可用时立即处理其输出,而不是等待整个过程完成:

process1 input_file.dat | process2 > output_file.dat

好处:

  • 通常快得多:
    • 不必写入磁盘或从磁盘读取(如果使用ramdisk,可以避免这种情况)。
    • 应该更完整地使用机器资源。
  • 完成后没有可删除的中间文件。
  • 无需像OP中那样进行复杂的锁定。

如果您没有直接创建管道的方法,但是拥有GNU coreutils,则可以使用以下方法:

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

无论第一个过程通过写入文件走了多远(即使它尚未开始或已经完成),这都将从头开始读取输入文件。


是的,那将是“显而易见的”解决方案。不幸的是,数据生成过程是我无法控制的(由其他用户运行)。
彼得·科瓦奇

@PeterKovac无关紧要的是:cat input_file.dat |
process2

@MariusMatutiae但catprocess2可能完成之前process1完成。他们不会阻止。
cpugeniusmv 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.