如何在Windows 10中使用python3.9检查文件是否在我的python脚本之外的另一个进程中打开?

ghg1uchk  于 2023-02-18  发布在  Python
关注(0)|答案(2)|浏览(158)

我正在使用以下函数允许操作系统打开与相关文件类型关联的第三方应用程序。例如:如果变量“fileToOpen”链接到一个名为flower.psd的文件(当然是完整路径),这个函数将在Windows中打开Photoshop,在Linux中打开Gimp(通常)。

def launchFile(fileToOpen):
    if platform.system() == 'Darwin':       # macOS
        subprocess.call(('open', fileToOpen))
    elif platform.system() == 'Windows':    # Windows
        os.startfile(fileToOpen)
    else:                                   # linux variants
        subprocess.call(('xdg-open', fileToOpen))

当它运行时,我希望有相同的python脚本监控该文件的使用,并在第三方应用程序使用完该文件后将其删除(意思是...第三方应用程序关闭了psd文件或第三方应用程序本身关闭并释放了该文件)。
我试过使用psutil和pywin 32,但似乎都不能在Windows 10和Python3.9中使用。有人成功地使用了这个吗?如果是的话,你是如何获得第三方应用程序的进程,同时又不会从Windows中获得权限错误的?
理想情况下,我希望得到一个可以在Windows、Mac和Linux上工作的解决方案,但目前我会接受Windows 10的任何帮助,因为通过***ps -ax的命令行帮助可以更容易地找到Mac和Linux|grep %文件名%***命令
请记住,这将理想地跟踪任何文件。TIA为您提供帮助。
根据请求更新:我试着将这段代码添加到我的代码中(来自之前的建议),即使这段代码单独出现在pythontest.py文件中,也会产生权限错误:

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)

下面代码不会显示错误,但不会检测到正在使用的文件:

import psutil
from pathlib import Path

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

thePath = Path("C:\\Users\\someUser\\Downloads\\Book1.xlsx")    
fileExists = has_handle(thePath)

if fileExists :
    print("This file is in use!")
else :
    print("This file is not in use")
92dk7w1h

92dk7w1h1#

找到了!另一篇文章的原始推荐忘记了一个函数..."Path"进程列表中的item. path作为字符串返回。这需要转换为Path对象,以便与您自己的路径对象进行比较。
因此这一行:

if fpath == item.path:

应为:

if fpath == Path(item.path):

下面是完整的代码:

import psutil
from pathlib import Path

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

    return False

thePath = Path("C:\\Users\\someUser\\Downloads\\Book1.xlsx")    
fileExists = has_handle(thePath)

if fileExists :
    print("This file is in use!")
else :
    print("This file is not in use")
    • 注意:**使用Path对象而不是字符串的原因是为了保持操作系统独立性。
z2acfund

z2acfund2#

基于@Frankie的回答,我编写了这个脚本。上面的脚本每个文件花费了16.1秒,因为proc.open_files()相当慢。
下面的脚本检查目录中的所有文件,并返回与每个打开的文件相关的pid。17个文件仅花费了
2.9s进行检查。这是因为proc.open如果文件的默认应用程序在内存中打开,则仅调用www.example.com _files()。
由于这是用来检查文件夹是否可以移动,该pid可以在以后用来
强制**关闭锁定应用程序,但请注意,该应用程序可能会打开其他文档,所有数据都将丢失。
这不会检测打开的txt文件,或者可能不会检测没有默认应用程序的文件

from pathlib import Path
import psutil
import os
import shlex
import winreg
from pprint import pprint as pp
from collections import defaultdict

class CheckFiles():
    def check_locked_files(self, path: str):
        '''Check all files recursivly in a directory and return a dict with the
           locked files associated with each pid (proocess id) 

        Args:
            path (str): root directory

        Returns:
            dict: dict(pid:[filenames])
        '''
        fnames = []
        apps = set()
        for root, _, f_names in os.walk(path):
            for f in f_names:
                f = Path(os.path.join(root, f))
                if self.is_file_in_use(f):
                    default_app = Path(self.get_default_windows_app(f.suffix)).name
                    apps.add(default_app)
                    fnames.append(str(f))
        if apps:
            return self.find_process(fnames, apps)

    def find_process(self, fnames: list[str], apps: set[str]):
        '''find processes for each locked files

        Args:
            fnames (list[str]): list of filepaths
            apps (set[str]): set of default apps

        Returns:
            dict: dict(pid:[filenames])
        '''
        open_files = defaultdict(list)
        for p in psutil.process_iter(['name']):
            name = p.info['name']
            if name in apps:
                try:
                    [open_files[p.pid].append(x.path) for x in p.open_files() if x.path in fnames]
                except:
                    continue
        return dict(open_files)
        

    def is_file_in_use(self, file_path: str):
        '''Check if file is in use by trying to rename it to its own name (nothing changes) but if
           locked then this will fail

        Args:
            file_path (str): _description_

        Returns:
            bool: True is file is locked by a process
        '''
        path = Path(file_path)
        
        if not path.exists():
            raise FileNotFoundError
        try:
            path.rename(path)
        except PermissionError:
            return True
        else:
            return False
    
    def get_default_windows_app(self, suffix: str):
        '''Find the default app dedicated to a file extension (suffix)

        Args:
            suffix (str): ie ".jpg"

        Returns:
            None|str: default app exe
        '''
        try:
            class_root = winreg.QueryValue(winreg.HKEY_CLASSES_ROOT, suffix)
            with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, r'{}\shell\open\command'.format(class_root)) as key:
                command = winreg.QueryValueEx(key, '')[0]
                return shlex.split(command)[0]
        except:
            return None
        

old_dir = r"C:\path_to_dir"

c = CheckFiles()
r = c.check_locked_files(old_dir)
pp(r)

相关问题