检查一个文件是否没有打开,也没有被其他进程使用

我的申请,我有以下要求: 1.有一个线程会定期将一些日志记录到文件中。日志文件将在一定的时间间隔内进行滚动。用于保持日志文件较小。 2.还有一个线程也会定期处理这些日志文件。将日志文件移动到其他位置,解析日志内容以生成一些日志报告。

但是,有一个条件是第二个线程无法处理用于记录日志的日志文件。在代码方面,伪代码类似如下:

#code in second thread to process the log files
for logFile in os.listdir(logFolder):
if not file_is_open(logFile) or file_is_use(logFile):
ProcessLogFile(logFile) # move log file to other place, and generate log report....

那么,我如何检查一个文件是否已经打开或者被其他进程使用? 我在互联网上做了一些研究,得到了一些结果:

try:
myfile = open(filename, "r+") # or "a+", whatever you need
except IOError:
print "Could not open file! Please close Excel!"

我试过这个代码,但是不管我使用“ r +”还是“ a +”标志,它都不起作用

try:
os.remove(filename) # try to remove it directly
except OSError as e:
if e.errno == errno.ENOENT: # file doesn't exist
break

这个代码可以工作,但它不能达到我的要求,因为我不想删除文件,以检查是否打开。

116768 次浏览

试图查明一个文件是否被另一个进程使用的问题是可能存在竞态条件。您可以检查一个文件,确定它不在使用中,然后在您打开它之前,另一个进程(或线程)跳入并抓取它(甚至删除它)。

好吧,假设你决定接受这种可能性,并希望它不会发生。检查其他进程正在使用的文件依赖于操作系统。

在 Linux 上相当容易,只需在/proc 中迭代 PID。下面是一个生成器,它对特定 PID 使用的文件进行迭代:

def iterate_fds(pid):
dir = '/proc/'+str(pid)+'/fd'
if not os.access(dir,os.R_OK|os.X_OK): return


for fds in os.listdir(dir):
for fd in fds:
full_name = os.path.join(dir, fd)
try:
file = os.readlink(full_name)
if file == '/dev/null' or \
re.match(r'pipe:\[\d+\]',file) or \
re.match(r'socket:\[\d+\]',file):
file = None
except OSError as err:
if err.errno == 2:
file = None
else:
raise(err)


yield (fd,file)

在 Windows 上,它不是那么简单,API 不会发布。有一个 sysinterals 工具(handle.exe)可以使用,但是我推荐 PyPi 模块 psutil,它是可移植的(例如,它也可以在 Linux 上运行,可能也可以在其他操作系统上运行) :

import psutil


for proc in psutil.process_iter():
try:
# this returns the list of opened files by the current process
flist = proc.open_files()
if flist:
print(proc.pid,proc.name)
for nt in flist:
print("\t",nt.path)


# This catches a race condition where a process ends
# before we can examine its files
except psutil.NoSuchProcess as err:
print("****",err)

您可以使用 Nofollow = “ nofollow”> inotify < a href = “ http://github.com/seb-m/pyinotify”rel = “ nofollow”> inotify 来监视文件系统中的活动。您可以观察文件关闭事件,指示已经发生了翻转。您还应该添加文件大小的附加条件。确保从第二个线程过滤掉文件关闭事件。

相反,在使用 os.remove ()时,你可以在 Windows 上使用以下解决方案:

import os


file = "D:\\temp\\test.pdf"
if os.path.exists(file):
try:
os.rename(file,file+"_")
print "Access on file \"" + str(file) +"\" is available!"
os.rename(file+"_",file)
except OSError as e:
message = "Access-error on file \"" + str(file) + "\"!!! \n" + str(e)
print message

我喜欢 丹尼尔的回答,但是对于 Windows 用户,我意识到将文件重命名为它已经有的名称更安全、更简单。这就解决了评论中对他的回答提出的问题。密码是这样的:

import os


f = 'C:/test.xlsx'
if os.path.exists(f):
try:
os.rename(f, f)
print 'Access on file "' + f +'" is available!'
except OSError as e:
print 'Access-error on file "' + f + '"! \n' + str(e)

您可以使用下一个函数检查一个文件是否有句柄(请记住传递该文件的完整路径) :

import psutil


def has_handle(fpath):
for proc in psutil.process_iter():
try:
for item in proc.open_files():
if fpath == item.path:
return True
except Exception:
pass


return False

我知道我迟到了,但我也有这个问题,我使用 伊索芙命令来解决它(我认为这是从上面提到的方法的新)。使用 伊索芙,我们基本上可以检查使用这个特定文件的进程。 我是这样做的:

from subprocess import check_output,Popen, PIPE
try:
lsout=Popen(['lsof',filename],stdout=PIPE, shell=False)
check_output(["grep",filename], stdin=lsout.stdout, shell=False)
except:
#check_output will throw an exception here if it won't find any process using that file

只要在“除外”部分中写入日志处理代码,就可以开始了。

一个稍微更精致的 来自上面的答案之一版本。

from pathlib import Path




def is_file_in_use(file_path):
path = Path(file_path)
    

if not path.exists():
raise FileNotFoundError
    

try:
path.rename(path)
except PermissionError:
return True
else:
return False

我提供了一个解决方案。请参阅以下代码。

def isFileinUsed(ifile):
widlcard = "/proc/*/fd/*"
lfds = glob.glob(widlcard)
for fds in lfds:
try:
file = os.readlink(fds)
if file == ifile:
return True
except OSError as err:
if err.errno == 2:
file = None
else:
raise(err)
return False

可以使用此函数检查文件是否已使用。

注: 此解决方案只能用于 Linux 系统。

窗户上,还可以利用 NTDLL/KerNEL32 Windows API 直接检索信息。以下代码返回 PID 列表,以防文件仍然被进程打开/使用(包括您自己的,如果您对文件有一个打开的句柄) :

import ctypes
from ctypes import wintypes


path = r"C:\temp\test.txt"


# -----------------------------------------------------------------------------
# generic strings and constants
# -----------------------------------------------------------------------------


ntdll = ctypes.WinDLL('ntdll')
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)


NTSTATUS = wintypes.LONG


INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
FILE_READ_ATTRIBUTES = 0x80
FILE_SHARE_READ = 1
OPEN_EXISTING = 3
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000


FILE_INFORMATION_CLASS = wintypes.ULONG
FileProcessIdsUsingFileInformation = 47


LPSECURITY_ATTRIBUTES = wintypes.LPVOID
ULONG_PTR = wintypes.WPARAM




# -----------------------------------------------------------------------------
# create handle on concerned file with dwDesiredAccess == FILE_READ_ATTRIBUTES
# -----------------------------------------------------------------------------


kernel32.CreateFileW.restype = wintypes.HANDLE
kernel32.CreateFileW.argtypes = (
wintypes.LPCWSTR,      # In     lpFileName
wintypes.DWORD,        # In     dwDesiredAccess
wintypes.DWORD,        # In     dwShareMode
LPSECURITY_ATTRIBUTES,  # In_opt lpSecurityAttributes
wintypes.DWORD,        # In     dwCreationDisposition
wintypes.DWORD,        # In     dwFlagsAndAttributes
wintypes.HANDLE)       # In_opt hTemplateFile
hFile = kernel32.CreateFileW(
path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, None, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, None)
if hFile == INVALID_HANDLE_VALUE:
raise ctypes.WinError(ctypes.get_last_error())




# -----------------------------------------------------------------------------
# prepare data types for system call
# -----------------------------------------------------------------------------


class IO_STATUS_BLOCK(ctypes.Structure):
class _STATUS(ctypes.Union):
_fields_ = (('Status', NTSTATUS),
('Pointer', wintypes.LPVOID))
_anonymous_ = '_Status',
_fields_ = (('_Status', _STATUS),
('Information', ULONG_PTR))




iosb = IO_STATUS_BLOCK()




class FILE_PROCESS_IDS_USING_FILE_INFORMATION(ctypes.Structure):
_fields_ = (('NumberOfProcessIdsInList', wintypes.LARGE_INTEGER),
('ProcessIdList', wintypes.LARGE_INTEGER * 64))




info = FILE_PROCESS_IDS_USING_FILE_INFORMATION()


PIO_STATUS_BLOCK = ctypes.POINTER(IO_STATUS_BLOCK)
ntdll.NtQueryInformationFile.restype = NTSTATUS
ntdll.NtQueryInformationFile.argtypes = (
wintypes.HANDLE,        # In  FileHandle
PIO_STATUS_BLOCK,       # Out IoStatusBlock
wintypes.LPVOID,        # Out FileInformation
wintypes.ULONG,         # In  Length
FILE_INFORMATION_CLASS)  # In  FileInformationClass


# -----------------------------------------------------------------------------
# system call to retrieve list of PIDs currently using the file
# -----------------------------------------------------------------------------
status = ntdll.NtQueryInformationFile(hFile, ctypes.byref(iosb),
ctypes.byref(info),
ctypes.sizeof(info),
FileProcessIdsUsingFileInformation)
pidList = info.ProcessIdList[0:info.NumberOfProcessIdsInList]
print(pidList)