file-type

Linux常用命令入门指南文档

版权申诉

RAR文件

27KB | 更新于2024-11-08 | 106 浏览量 | 0 下载量 举报 收藏
download 限时特惠:#14.90
Linux作为一款开源的类Unix操作系统,因其稳定、高效、可定制性强等特点,在服务器市场和桌面市场都拥有广泛的用户群体。对于初学者来说,掌握一些基础的Linux命令是进行进一步学习和开发的前提。文档《Linux 入门常用命令.doc》旨在帮助初学者快速学习和掌握Linux系统中的基础命令,并通过实例加深理解。 一、Linux命令结构基础 Linux命令通常遵循一定的格式,最简单的形式为: ``` command [options] [arguments] ``` 其中,command代表具体的命令名称;options是该命令的可选参数,通常用于改变命令的行为或获取更多的输出信息;arguments则是命令作用的对象,可以是文件、目录或其他任何命令可以接受的内容。 二、基本操作命令 1. cd:改变当前工作目录。 示例:`cd /home/user`,切换到/home/user目录。 2. pwd:显示当前工作目录的路径。 示例:`pwd`,在终端输出当前目录的完整路径。 3. ls:列出目录内容。 示例:`ls -l`,以长格式列出目录下的文件和文件夹详细信息。 4. cp:复制文件或目录。 示例:`cp source.txt destination.txt`,将source.txt复制为destination.txt。 5. mv:移动或重命名文件或目录。 示例:`mv oldname.txt newname.txt`,将oldname.txt重命名为newname.txt。 6. rm:删除文件或目录。 示例:`rm unwanted.txt`,删除文件unwanted.txt。 三、文件操作命令 1. touch:创建一个空文件或修改文件的访问和修改时间。 示例:`touch newfile.txt`,创建一个名为newfile.txt的空文件。 2. cat:查看文件内容、创建文件、文件合并、追加文件内容等功能。 示例:`cat file.txt`,查看文件file.txt的内容。 3. more/less:分页显示文件内容。 示例:`more file.txt`,分页显示file.txt的内容。 4. head/tail:查看文件开头或结尾的内容。 示例:`tail -n 10 file.txt`,查看file.txt文件的最后10行。 四、权限管理命令 1. chmod:改变文件或目录的权限。 示例:`chmod 755 file.txt`,将file.txt的权限设置为可读、可写、可执行对所有用户。 2. chown:改变文件或目录的所有者。 示例:`chown username file.txt`,将file.txt的所有者改为username。 3. chgrp:改变文件或目录的所属群组。 示例:`chgrp groupname file.txt`,将file.txt的所属群组改为groupname。 五、系统管理命令 1. ps:显示当前系统的进程状态。 示例:`ps aux`,显示系统上所有进程的详细信息。 ***:动态显示进程状态。 示例:直接运行`top`命令,实时显示进程状态。 3. df:显示磁盘空间的使用情况。 示例:`df -h`,以易读的格式显示磁盘空间使用情况。 4. free:显示系统的内存使用情况。 示例:`free -m`,以兆字节为单位显示内存使用情况。 5. kill:终止进程。 示例:`kill -9 PID`,强制终止进程号为PID的进程。 六、网络管理命令 1. ifconfig:配置或显示Linux系统的网络接口。 示例:`ifconfig`,显示所有网络接口的信息。 2. ping:测试网络连接。 示例:`***`,测试与***的网络连接。 ***stat:显示网络连接、路由表、接口统计、伪装连接和多播成员。 示例:`netstat -tuln`,显示TCP和UDP连接的状态信息。 4. ssh:安全地访问远程服务器。 示例:`ssh user@remote_server`,以用户user的身份登录到remote_server。 5.scp:安全地在本地和远程服务器之间复制文件。 示例:`scp file.txt user@remote_server:/home/user/`,将file.txt复制到远程服务器的/home/user/目录。 七、软件管理命令 1. apt-get:Debian和Ubuntu系列Linux发行版的包管理工具。 示例:`apt-get update`,更新软件包列表。 2. yum:RedHat和CentOS系列Linux发行版的包管理工具。 示例:`yum update`,更新所有已安装的软件包。 3. rpm:用于管理rpm格式的软件包。 示例:`rpm -ivh package.rpm`,安装名为package.rpm的软件包。 通过这些基础命令的掌握,Linux初学者可以进行日常的基本操作,为深入学习Linux打下良好的基础。随着经验的增长,还可以学习更复杂的命令和脚本编写,从而提高工作效率和自动化管理能力。

相关推荐

filetype

import os import re import sys import time import threading import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext from tkinter.font import Font import fnmatch import subprocess import shutil import docx from openpyxl import load_workbook import PyPDF2 import zipfile import chardet import xlrd # 添加对旧版Excel的支持 class FileSearchApp: def __init__(self, master): self.master = master master.title("高级文件搜索工具") master.geometry("1200x800") master.minsize(900, 650) # 设置现代主题 self.style = ttk.Style() self.style.theme_use("vista" if sys.platform == "win32" else "aqua") # 创建主框架 main_frame = ttk.Frame(master, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 创建左侧搜索面板 search_frame = ttk.LabelFrame(main_frame, text="搜索选项", padding=10) search_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10), pady=5) # 使用网格布局管理器 row = 0 ttk.Label(search_frame, text="搜索目录:").grid(row=row, column=0, sticky="w", pady=5) self.dir_entry = ttk.Entry(search_frame, width=40) self.dir_entry.grid(row=row, column=1, padx=5, pady=5, sticky="we") self.dir_entry.insert(0, os.getcwd()) ttk.Button(search_frame, text="浏览...", command=self.browse_directory).grid(row=row, column=2, padx=5, pady=5) row += 1 ttk.Label(search_frame, text="关键词:").grid(row=row, column=0, sticky="w", pady=5) self.keyword_entry = ttk.Entry(search_frame, width=40) self.keyword_entry.grid(row=row, column=1, padx=5, pady=5, sticky="we") row += 1 ttk.Label(search_frame, text="文件过滤:").grid(row=row, column=0, sticky="w", pady=5) self.filter_entry = ttk.Entry(search_frame, width=40) self.filter_entry.grid(row=row, column=1, padx=5, pady=5, sticky="we") self.filter_entry.insert(0, "*") row += 1 # 添加分隔线 ttk.Separator(search_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=3, sticky="ew", pady=10) row += 1 # 搜索选项 options_frame = ttk.Frame(search_frame) options_frame.grid(row=row, column=0, columnspan=3, sticky="we", padx=5, pady=5) # 使用网格布局替代pack布局,更紧凑 self.case_var = tk.BooleanVar(value=False) ttk.Checkbutton(options_frame, text="忽略大小写", variable=self.case_var).grid(row=0, column=0, sticky="w", padx=(0, 10)) self.regex_var = tk.BooleanVar(value=False) ttk.Checkbutton(options_frame, text="正则表达式", variable=self.regex_var).grid(row=0, column=1, sticky="w", padx=(0, 10)) self.binary_var = tk.BooleanVar(value=False) self.binary_check = ttk.Checkbutton(options_frame, text="包含二进制", variable=self.binary_var) self.binary_check.grid(row=0, column=2, sticky="w") # 添加文件大小限制选项 self.limit_var = tk.BooleanVar(value=True) ttk.Checkbutton(options_frame, text="限制大小(100MB)", variable=self.limit_var).grid(row=0, column=3, sticky="w", padx=(10, 0)) row += 1 # 添加分隔线 ttk.Separator(search_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=3, sticky="ew", pady=10) row += 1 # 搜索按钮 button_frame = ttk.Frame(search_frame) button_frame.grid(row=row, column=0, columnspan=3, pady=10) self.search_button = ttk.Button(button_frame, text="开始搜索", command=self.start_search) self.search_button.pack(side=tk.LEFT, padx=5) self.stop_button = ttk.Button(button_frame, text="停止搜索", command=self.stop_search, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=5) self.export_button = ttk.Button(button_frame, text="导出结果", command=self.export_results) self.export_button.pack(side=tk.LEFT, padx=5) row += 1 # 添加分隔线 ttk.Separator(search_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=3, sticky="ew", pady=10) row += 1 # 状态栏 status_frame = ttk.Frame(search_frame) status_frame.grid(row=row, column=0, columnspan=3, sticky="we", pady=5) # 状态标签(左对齐) self.status_label = ttk.Label(status_frame, text="就绪", font=("Arial", 9)) self.status_label.pack(side=tk.LEFT, anchor='w') # 进度条(中间,可伸缩) self.progress_var = tk.DoubleVar() self.progress_bar = ttk.Progressbar( status_frame, variable=self.progress_var, length=200, mode='determinate' ) self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) # 结果统计(右对齐) self.stats_label = ttk.Label(status_frame, text="", font=("Arial", 9)) self.stats_label.pack(side=tk.RIGHT) # 创建结果面板 results_frame = ttk.LabelFrame(main_frame, text="搜索结果", padding=10) results_frame.pack(fill=tk.BOTH, expand=True, padx=(5, 0), pady=5) # 分割窗格 paned_window = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) paned_window.pack(fill=tk.BOTH, expand=True) # 左侧文件列表 file_list_frame = ttk.Frame(paned_window) paned_window.add(file_list_frame, weight=1) # 使用Treeview替代Listbox columns = ("filename", "path") self.file_tree = ttk.Treeview(file_list_frame, columns=columns, show="headings", selectmode="browse") # 设置列标题 self.file_tree.heading("filename", text="文件名") self.file_tree.heading("path", text="路径") # 设置列宽 self.file_tree.column("filename", width=200, anchor="w") self.file_tree.column("path", width=300, anchor="w") self.file_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.file_tree.bind('<<TreeviewSelect>>', self.show_file_content) self.file_tree.bind('<Double-1>', self.open_selected_file) file_scroll = ttk.Scrollbar(file_list_frame, command=self.file_tree.yview) file_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.file_tree.config(yscrollcommand=file_scroll.set) # 右键菜单 self.file_menu = tk.Menu(self.master, tearoff=0) self.file_menu.add_command(label="打开文件", command=self.open_selected_file) self.file_menu.add_command(label="打开文件位置", command=self.open_file_location) self.file_tree.bind("<Button-3>", self.show_file_context_menu) # 右侧文件内容预览 content_frame = ttk.Frame(paned_window) paned_window.add(content_frame, weight=2) self.content_text = scrolledtext.ScrolledText( content_frame, wrap=tk.WORD, font=("Consolas", 10), padx=5, pady=5 ) self.content_text.pack(fill=tk.BOTH, expand=True) # 文本区域右键菜单 text_menu = tk.Menu(self.master, tearoff=0) text_menu.add_command(label="复制", command=self.copy_selected_text) self.content_text.bind("<Button-3>", lambda e: text_menu.tk_popup(e.x_root, e.y_root)) # 高亮标签 self.content_text.tag_configure("match", background="yellow") self.content_text.tag_configure("linenum", foreground="blue") self.content_text.tag_configure("header", foreground="darkgreen", font=("Arial", 10, "bold")) self.content_text.tag_configure("warning", foreground="red", font=("Arial", 10, "italic")) # 初始化变量 self.results = {} self.all_files = [] self.file_paths = [] self.stop_requested = False self.search_thread = None def browse_directory(self): directory = filedialog.askdirectory(title="选择搜索目录") if directory: self.dir_entry.delete(0, tk.END) self.dir_entry.insert(0, directory) def start_search(self): # 重置状态 self.progress_var.set(0) self.stop_requested = False self.results = {} self.all_files = [] self.file_paths = [] self.file_tree.delete(*self.file_tree.get_children()) self.content_text.delete(1.0, tk.END) self.status_label.config(text="正在搜索...") self.search_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.stats_label.config(text="") # 获取搜索参数 directory = self.dir_entry.get().strip() keyword = self.keyword_entry.get().strip() file_filter = self.filter_entry.get().strip() # 验证输入 if not directory or not os.path.isdir(directory): messagebox.showerror("错误", "请选择有效的搜索目录") self.search_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) return if not keyword: messagebox.showerror("错误", "请输入搜索关键词") self.search_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) return # 解析文件过滤器 if file_filter == "": filter_patterns = ['*'] else: separators = [';', '|', ' ', ','] for sep in separators: if sep in file_filter: filter_patterns = [pat.strip() for pat in file_filter.split(sep)] break else: filter_patterns = [file_filter] # 编译搜索模式 flags = re.IGNORECASE if self.case_var.get() else 0 try: if self.regex_var.get(): pattern = re.compile(keyword, flags) else: escaped_keyword = re.escape(keyword) pattern = re.compile(escaped_keyword, flags) except re.error as e: messagebox.showerror("正则表达式错误", f"无效的正则表达式: {str(e)}") self.search_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) return # 在后台线程中执行搜索 self.search_thread = threading.Thread( target=self.perform_search, args=(directory, filter_patterns, pattern), daemon=True ) self.search_thread.start() def perform_search(self, directory, filter_patterns, pattern): """在后台线程中执行文件搜索""" try: # 收集所有匹配的文件 all_files = [] for root, _, files in os.walk(directory): if self.stop_requested: self.master.after(0, lambda: self.status_label.config(text="搜索已取消")) return for file in files: file_path = os.path.join(root, file) # 检查文件大小限制(避免处理超大文件) try: file_size = os.path.getsize(file_path) if file_size > 100 * 1024 * 1024: # 100MB continue except: continue # 检查是否符合任一过滤模式 if any(fnmatch.fnmatch(file, pat) for pat in filter_patterns): all_files.append(file_path) self.all_files = all_files total_files = len(all_files) # 初始化进度条 self.master.after(0, lambda: self.progress_bar.config(maximum=total_files)) self.master.after(0, lambda: self.stats_label.config(text=f"扫描到 {total_files} 个文件")) # 搜索每个文件 self.results = {} processed = 0 matches_found = 0 for file_path in self.all_files: if self.stop_requested: break processed += 1 # 更新进度条(安全方式) self.master.after(0, lambda v=processed: self.progress_var.set(v)) if processed % 10 == 0: # 每处理10个文件更新一次进度 self.master.after(0, lambda p=processed, t=total_files: self.stats_label.config(text=f"处理中: {p}/{t} 文件 ({round(p/t*100,1)}%)")) # 忽略二进制文件(除非用户选择包含) if not self.binary_var.get() and self.is_binary(file_path): continue # 获取文件扩展名 _, ext = os.path.splitext(file_path) ext_lower = ext.lower() # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.search_in_office_file(file_path, pattern) # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.search_in_archive(file_path, pattern) # 处理文本文件 else: matches = self.search_in_text_file(file_path, pattern) if matches: self.results[file_path] = matches matches_found += len(matches) # 在UI线程中添加文件到列表 filename = os.path.basename(file_path) self.master.after(0, lambda fp=file_path, fn=filename: self.file_tree.insert("", "end", values=(fn, fp))) # 更新完成状态 if self.stop_requested: status_text = f"搜索已取消 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" else: status_text = f"搜索完成 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" self.master.after(0, lambda: self.status_label.config(text=status_text)) self.master.after(0, lambda: self.stats_label.config(text=f"已处理 {processed}/{total_files} 文件")) self.master.after(0, lambda: self.progress_var.set(total_files)) except Exception as e: # 记录详细错误日志 error_info = f"搜索错误: {type(e).__name__} - {str(e)}" print(error_info) with open("search_errors.log", "a") as log: log.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_info}\n") import traceback traceback.print_exc(file=log) self.master.after(0, lambda: messagebox.showerror( "搜索错误", f"发生严重错误: {error_info}\n详细信息已记录到日志" )) finally: self.master.after(0, lambda: self.search_button.config(state=tk.NORMAL)) self.master.after(0, lambda: self.stop_button.config(state=tk.DISABLED)) self.search_thread = None def search_in_text_file(self, filepath, pattern): """在文本文件中搜索匹配项""" matches = [] try: encoding = self.detect_encoding(filepath) try: with open(filepath, 'r', encoding=encoding, errors='replace') as f: for line_num, line in enumerate(f, 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except UnicodeDecodeError: # 特殊编码处理回退 with open(filepath, 'rb') as f: content = f.read() try: text = content.decode('utf-8', errors='replace') except: text = content.decode('latin-1', errors='replace') for line_num, line in enumerate(text.splitlines(), 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"读取文本文件失败 {filepath}: {str(e)}") return matches def search_in_office_file(self, filepath, pattern): """在Office文件中搜索文本内容""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # DOCX文件处理 if ext_lower == '.docx': doc = docx.Document(filepath) # 搜索段落 for i, para in enumerate(doc.paragraphs, 1): if para.text and pattern.search(para.text): matches.append((i, f"段落 {i}: {para.text[:100]}" + ("..." if len(para.text) > 100 else ""))) # 搜索表格 for table in doc.tables: for row_idx, row in enumerate(table.rows, 1): for cell_idx, cell in enumerate(row.cells, 1): if cell.text and pattern.search(cell.text): content = cell.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((row_idx, f"表格 行{row_idx}列{cell_idx}: {content}")) # XLSX/XLS文件处理 elif ext_lower in ('.xlsx', '.xls', '.xlsm'): # 处理新格式Excel文件 if ext_lower in ('.xlsx', '.xlsm'): wb = load_workbook(filepath, read_only=True, data_only=True) for sheet_name in wb.sheetnames: sheet = wb[sheet_name] for row_idx, row in enumerate(sheet.iter_rows(values_only=True), 1): for col_idx, cell in enumerate(row, 1): if cell is not None and pattern.search(str(cell)): cell_ref = f"{chr(64+col_idx)}{row_idx}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx, f"工作表 '{sheet_name}' 单元格 {cell_ref}: {cell_value}")) # 处理旧格式Excel文件 elif ext_lower == '.xls': wb = xlrd.open_workbook(filepath) for sheet_idx in range(wb.nsheets): sheet = wb.sheet_by_index(sheet_idx) for row_idx in range(sheet.nrows): for col_idx in range(sheet.ncols): cell = sheet.cell_value(row_idx, col_idx) if cell and pattern.search(str(cell)): cell_ref = f"{chr(65+col_idx)}{row_idx+1}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx+1, f"工作表 '{sheet.name}' 单元格 {cell_ref}: {cell_value}")) # PPTX文件处理 elif ext_lower == '.pptx': from pptx import Presentation ppt = Presentation(filepath) # 搜索幻灯片文本 for slide_idx, slide in enumerate(ppt.slides, 1): for shape in slide.shapes: if hasattr(shape, "text"): if shape.text and pattern.search(shape.text): content = shape.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((slide_idx, f"幻灯片 {slide_idx}: {content}")) # PDF文件处理 elif ext_lower == '.pdf': with open(filepath, 'rb') as f: pdf = PyPDF2.PdfReader(f) for page_num in range(len(pdf.pages)): page_text = pdf.pages[page_num].extract_text() if page_text and pattern.search(page_text): # 提取匹配内容 matches_found = [] for match in pattern.finditer(page_text): context = page_text[max(0, match.start()-20):match.end()+20] context = context.replace('\n', ' ').strip() matches_found.append(context) # 添加到结果 if matches_found: preview = "; ".join(matches_found[:3]) # 显示前3个匹配 if len(matches_found) > 3: preview += f" ... (+{len(matches_found)-3} 更多)" matches.append((page_num+1, f"页面 {page_num+1}: {preview}")) # 旧版DOC文件处理 elif ext_lower == '.doc': try: # 尝试使用antiword转换DOC为文本 result = subprocess.run(['antiword', filepath], capture_output=True, text=True, timeout=10) if result.returncode == 0: doc_text = result.stdout for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception: # 备用方法:使用python-doc处理 import win32com.client word = win32com.client.Dispatch("Word.Application") word.Visible = False doc = word.Documents.Open(filepath) doc_text = doc.Content.Text doc.Close() word.Quit() for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"处理Office文件失败 {filepath}: {str(e)}") return matches def search_in_archive(self, filepath, pattern): """在压缩文件中搜索匹配项""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # ZIP文件处理 if ext_lower in ('.zip', '.jar', '.war'): with zipfile.ZipFile(filepath, 'r') as archive: for name in archive.namelist(): # 只处理文本文件和Office文档 if not name.endswith(('/')) and not self.is_binary(name): try: with archive.open(name) as file: content = file.read(4096) # 只读取前4KB # 尝试检测编码 result = chardet.detect(content) encoding = result['encoding'] if result['confidence'] > 0.7 else 'utf-8' # 解码内容并搜索 try: text_content = content.decode(encoding, errors='replace') if pattern.search(text_content): matches.append((name, f"压缩文件中的文件: {name}")) except: # 二进制内容搜索 if pattern.search(content): matches.append((name, f"压缩文件中的文件(二进制内容): {name}")) except Exception: continue # 其他压缩格式(需要外部工具) elif ext_lower in ('.rar', '.7z', '.tar', '.gz'): # 使用7zip命令行工具解压并搜索 temp_dir = tempfile.mkdtemp() try: subprocess.run(['7z', 'x', filepath, f'-o{temp_dir}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=60) # 递归搜索解压的目录 for root, _, files in os.walk(temp_dir): for file in files: full_path = os.path.join(root, file) _, file_ext = os.path.splitext(file) file_ext = file_ext.lower() # 只在文本/Office文件中搜索 if file_ext in ['', '.txt', '.py', '.java', '.c', '.cpp', '.h', '.html', '.xml', '.json', '.csv', '.docx', '.xlsx', '.pptx', '.pdf']: if file_ext in ['.docx', '.xlsx', '.pptx', '.pdf']: file_matches = self.search_in_office_file(full_path, pattern) else: file_matches = self.search_in_text_file(full_path, pattern) if file_matches: matches.append((file, f"压缩文件中的文件: {file}")) finally: shutil.rmtree(temp_dir, ignore_errors=True) except Exception as e: print(f"处理压缩文件失败 {filepath}: {str(e)}") return matches def detect_encoding(self, filepath): """改进的文件编码检测方法""" try: # 尝试读取文件前4KB进行编码检测 with open(filepath, 'rb') as f: raw_data = f.read(4096) # 使用chardet进行编码检测 result = chardet.detect(raw_data) # 优先使用检测到的编码,否则尝试常见编码 if result['confidence'] > 0.7: return result['encoding'] # 中文环境常用编码回退策略 common_encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1'] for encoding in common_encodings: try: # 尝试解码验证 raw_data.decode(encoding, errors='strict') return encoding except UnicodeDecodeError: continue # 默认使用UTF-8 return 'utf-8' except Exception: return 'utf-8' def is_binary(self, filepath): """检查文件是否为二进制文件""" try: with open(filepath, 'rb') as f: chunk = f.read(1024) if b'\0' in chunk: # 空字节是二进制文件的标志 return True # 检查高字节值 if any(byte >= 0x80 for byte in chunk): return True return False except: return False def stop_search(self): """停止当前搜索""" self.stop_requested = True self.status_label.config(text="正在停止搜索...") self.stop_button.config(state=tk.DISABLED) # 冻结进度条显示当前进度 self.progress_bar.config(mode='indeterminate' if self.progress_var.get() == 0 else 'determinate') def export_results(self): """导出搜索结果""" if not self.results: messagebox.showinfo("导出结果", "没有可导出的搜索结果") return file_path = filedialog.asksaveasfilename( title="保存搜索结果为", defaultextension=".csv", filetypes=[("CSV 文件", "*.csv"), ("文本文件", "*.txt")] ) if not file_path: return try: with open(file_path, 'w', encoding='utf-8') as f: # 写出CSV头部 f.write("文件路径,匹配行号,匹配内容\n") # 写出每项结果 for file, matches in self.results.items(): for line_num, match_content in matches: # 清理内容中的逗号 cleaned_content = match_content.replace('"', '""').replace(',', ';') f.write(f'"{file}",{line_num},"{cleaned_content}"\n') messagebox.showinfo("导出成功", f"搜索结果已保存到:\n{file_path}") except Exception as e: messagebox.showerror("导出错误", f"导出失败: {str(e)}") def show_file_content(self, event=None): """在预览区域显示文件内容""" # 获取选中的文件 selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] # 清空预览区域 self.content_text.delete(1.0, tk.END) # 获取文件扩展名 _, ext = os.path.splitext(filepath) ext_lower = ext.lower() # 显示文件路径标题 self.content_text.insert(tk.END, f"文件路径: {filepath}\n", "header") # 处理不同文件类型 try: # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.results.get(filepath, []) if not matches: self.content_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.content_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") # 显示每个匹配项 for i, (line_num, content) in enumerate(matches, 1): self.content_text.insert(tk.END, f"[匹配项 {i}] 位置: {line_num}\n") self.content_text.insert(tk.END, f"{content}\n\n") # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.results.get(filepath, []) if not matches: self.content_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.content_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") for i, (file_in_zip, content) in enumerate(matches, 1): self.content_text.insert(tk.END, f"[匹配项 {i}] 文件: {file_in_zip}\n") self.content_text.insert(tk.END, f"{content}\n\n") # 处理文本文件 else: # 获取关键词高亮模式 keyword = self.keyword_entry.get().strip() flags = re.IGNORECASE if self.case_var.get() else 0 if self.regex_var.get(): try: pattern = re.compile(keyword, flags) except: pattern = None else: pattern = re.compile(re.escape(keyword), flags) # 显示文件内容并高亮匹配 self.content_text.insert(tk.END, "\n文件内容:\n\n", "header") # 限制预览内容大小(最多显示1000行) max_preview_lines = 1000 try: encoding = self.detect_encoding(filepath) with open(filepath, 'r', encoding=encoding, errors='replace') as f: line_count = 0 for line in f: line_count += 1 if line_count > max_preview_lines: self.content_text.insert(tk.END, f"\n... (文件过大,仅显示前{max_preview_lines}行)\n", "warning") break # 插入行号 self.content_text.insert(tk.END, f"{line_count:4d} | ", "linenum") # 插入行内容并高亮匹配 if pattern: start_idx = 0 for match in pattern.finditer(line): # 插入匹配前的文本 self.content_text.insert(tk.END, line[start_idx:match.start()]) # 插入高亮的匹配文本 self.content_text.insert(tk.END, match.group(), "match") start_idx = match.end() # 插入匹配后的文本 self.content_text.insert(tk.END, line[start_idx:]) else: self.content_text.insert(tk.END, line) except UnicodeDecodeError: self.content_text.insert(tk.END, "\n无法解码此文件内容(可能是二进制文件)\n", "warning") except Exception as e: self.content_text.insert(tk.END, f"\n读取文件时出错: {str(e)}\n", "warning") except Exception as e: self.content_text.insert(tk.END, f"\n加载文件内容出错: {str(e)}\n", "warning") def open_selected_file(self, event=None): """用系统默认程序打开选中的文件""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] try: if sys.platform == "win32": os.startfile(filepath) elif sys.platform == "darwin": # macOS subprocess.run(["open", filepath]) else: # Linux subprocess.run(["xdg-open", filepath]) except Exception as e: messagebox.showerror("打开文件失败", f"无法打开文件: {str(e)}") def open_file_location(self): """在文件资源管理器中打开文件所在位置""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] folder = os.path.dirname(filepath) try: if sys.platform == "win32": subprocess.run(["explorer", "/select,", filepath]) elif sys.platform == "darwin": # macOS subprocess.run(["open", "-R", filepath]) else: # Linux subprocess.run(["xdg-open", folder]) except Exception as e: messagebox.showerror("打开位置失败", f"无法打开位置: {str(e)}") def show_file_context_menu(self, event): """显示文件列表的右键菜单""" item = self.file_tree.identify_row(event.y) if item: self.file_tree.selection_set(item) self.file_menu.tk_popup(event.x_root, event.y_root) def copy_selected_text(self): """复制预览区域中选中的文本""" selected_text = self.content_text.get(tk.SEL_FIRST, tk.SEL_LAST) if selected_text: self.master.clipboard_clear() self.master.clipboard_append(selected_text) # 程序入口 if __name__ == "__main__": root = tk.Tk() app = FileSearchApp(root) # 添加图标(如果有) try: if sys.platform == "win32": root.iconbitmap("search_icon.ico") else: img = tk.PhotoImage(file="search_icon.png") root.iconphoto(True, img) except: pass root.mainloop() 这是我的代码,优化布局使其更美观,增加颜色区分,增加对关键字的高亮

filetype

这是我的日志:[Running] python -u "e:\system\Desktop\项目所需文件\工具\文件搜索工具\File Content Search Tool.py" libpng warning: iCCP: known incorrect sRGB profile libpng warning: iCCP: known incorrect sRGB profile Exception in thread Thread-1 (perform_search): Traceback (most recent call last): File "e:\system\Desktop\\u9879�ڏ�������\�H��\�����r���H��\File Content Search Tool.py", line 437, in search_in_office_file wb = load_workbook(filepath, read_only=True, data_only=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\site-packages\openpyxl\reader\excel.py", line 346, in load_workbook reader = ExcelReader(filename, read_only, keep_vba, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\site-packages\openpyxl\reader\excel.py", line 123, in __init__ self.archive = _validate_archive(fn) ^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\site-packages\openpyxl\reader\excel.py", line 95, in _validate_archive archive = ZipFile(filename, 'r') ^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\zipfile\__init__.py", line 1331, in __init__ self.fp = io.open(file, filemode) ^^^^^^^^^^^^^^^^^^^^^^^ PermissionError: [Errno 13] Permission denied: 'E:/SVN/DH_D82D_D83D/trunk/10COMMUNICATION/1001Minutes/12.DRBFM\\~$D82DD83D_CANDI�Ή�_DRBFM.xlsm' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "e:\system\Desktop\\u9879�ڏ�������\�H��\�����r���H��\File Content Search Tool.py", line 330, in perform_search matches = self.search_in_office_file(file_path, pattern) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "e:\system\Desktop\\u9879�ڏ�������\�H��\�����r���H��\File Content Search Tool.py", line 534, in search_in_office_file print(f"\u5904��Office������\u8d25 {filepath}: {str(e)}") UnicodeEncodeError: 'cp932' codec can't encode character '\u5904' in position 0: illegal multibyte sequence During handling of the above exception, another exception occurred: Traceback (most recent call last): File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1075, in _bootstrap_inner self.run() File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1012, in run self._target(*self._args, **self._kwargs) File "e:\system\Desktop\\u9879�ڏ�������\�H��\�����r���H��\File Content Search Tool.py", line 359, in perform_search print(error_info) UnicodeEncodeError: 'cp932' codec can't encode character '\u9519' in position 2: illegal multibyte sequence 这是我的代码:import os import re import sys import time import threading import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext from tkinter.font import Font import fnmatch import subprocess import shutil import docx from openpyxl import load_workbook import PyPDF2 import zipfile import chardet import xlrd # 添加对旧版Excel的支持 class FileSearchApp: def __init__(self, master): self.master = master master.title("高级文件搜索工具") master.geometry("1200x800") master.minsize(900, 650) # 设置现代主题 self.style = ttk.Style() self.style.theme_use("vista" if sys.platform == "win32" else "aqua") # 创建主框架 main_frame = ttk.Frame(master, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 创建左侧搜索面板 search_frame = ttk.LabelFrame(main_frame, text="搜索选项", padding=10) search_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10), pady=5) # 使用网格布局管理器 row = 0 ttk.Label(search_frame, text="搜索目录:").grid(row=row, column=0, sticky="w", pady=5) self.dir_entry = ttk.Entry(search_frame, width=40) self.dir_entry.grid(row=row, column=1, padx=5, pady=5, sticky="we") self.dir_entry.insert(0, os.getcwd()) ttk.Button(search_frame, text="浏览...", command=self.browse_directory).grid(row=row, column=2, padx=5, pady=5) row += 1 ttk.Label(search_frame, text="关键词:").grid(row=row, column=0, sticky="w", pady=5) self.keyword_entry = ttk.Entry(search_frame, width=40) self.keyword_entry.grid(row=row, column=1, padx=5, pady=5, sticky="we") row += 1 ttk.Label(search_frame, text="文件过滤:").grid(row=row, column=0, sticky="w", pady=5) self.filter_entry = ttk.Entry(search_frame, width=40) self.filter_entry.grid(row=row, column=1, padx=5, pady=5, sticky="we") self.filter_entry.insert(0, "*") row += 1 # 添加分隔线 ttk.Separator(search_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=3, sticky="ew", pady=10) row += 1 # 搜索选项 options_frame = ttk.Frame(search_frame) options_frame.grid(row=row, column=0, columnspan=3, sticky="we", padx=5, pady=5) # 使用网格布局替代pack布局,更紧凑 self.case_var = tk.BooleanVar(value=False) ttk.Checkbutton(options_frame, text="忽略大小写", variable=self.case_var).grid(row=0, column=0, sticky="w", padx=(0, 10)) self.regex_var = tk.BooleanVar(value=False) ttk.Checkbutton(options_frame, text="正则表达式", variable=self.regex_var).grid(row=0, column=1, sticky="w", padx=(0, 10)) self.binary_var = tk.BooleanVar(value=False) self.binary_check = ttk.Checkbutton(options_frame, text="包含二进制", variable=self.binary_var) self.binary_check.grid(row=0, column=2, sticky="w") # 添加文件大小限制选项 self.limit_var = tk.BooleanVar(value=True) ttk.Checkbutton(options_frame, text="限制大小(100MB)", variable=self.limit_var).grid(row=0, column=3, sticky="w", padx=(10, 0)) row += 1 # 添加分隔线 ttk.Separator(search_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=3, sticky="ew", pady=10) row += 1 # 搜索按钮 button_frame = ttk.Frame(search_frame) button_frame.grid(row=row, column=0, columnspan=3, pady=10) self.search_button = ttk.Button(button_frame, text="开始搜索", command=self.start_search) self.search_button.pack(side=tk.LEFT, padx=5) self.stop_button = ttk.Button(button_frame, text="停止搜索", command=self.stop_search, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=5) self.export_button = ttk.Button(button_frame, text="导出结果", command=self.export_results) self.export_button.pack(side=tk.LEFT, padx=5) row += 1 # 添加分隔线 ttk.Separator(search_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=3, sticky="ew", pady=10) row += 1 # 状态栏 status_frame = ttk.Frame(search_frame) status_frame.grid(row=row, column=0, columnspan=3, sticky="we", pady=5) # 状态标签(左对齐) self.status_label = ttk.Label(status_frame, text="就绪", font=("Arial", 9)) self.status_label.pack(side=tk.LEFT, anchor='w') # 进度条(中间,可伸缩) self.progress_var = tk.DoubleVar() self.progress_bar = ttk.Progressbar( status_frame, variable=self.progress_var, length=200, mode='determinate' ) self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) # 结果统计(右对齐) self.stats_label = ttk.Label(status_frame, text="", font=("Arial", 9)) self.stats_label.pack(side=tk.RIGHT) # 创建结果面板 results_frame = ttk.LabelFrame(main_frame, text="搜索结果", padding=10) results_frame.pack(fill=tk.BOTH, expand=True, padx=(5, 0), pady=5) # 分割窗格 paned_window = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) paned_window.pack(fill=tk.BOTH, expand=True) # 左侧文件列表 file_list_frame = ttk.Frame(paned_window) paned_window.add(file_list_frame, weight=1) # 使用Treeview替代Listbox columns = ("filename", "path") self.file_tree = ttk.Treeview(file_list_frame, columns=columns, show="headings", selectmode="browse") # 设置列标题 self.file_tree.heading("filename", text="文件名") self.file_tree.heading("path", text="路径") # 设置列宽 self.file_tree.column("filename", width=200, anchor="w") self.file_tree.column("path", width=300, anchor="w") self.file_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.file_tree.bind('<<TreeviewSelect>>', self.show_file_content) self.file_tree.bind('<Double-1>', self.open_selected_file) file_scroll = ttk.Scrollbar(file_list_frame, command=self.file_tree.yview) file_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.file_tree.config(yscrollcommand=file_scroll.set) # 右键菜单 self.file_menu = tk.Menu(self.master, tearoff=0) self.file_menu.add_command(label="打开文件", command=self.open_selected_file) self.file_menu.add_command(label="打开文件位置", command=self.open_file_location) self.file_tree.bind("<Button-3>", self.show_file_context_menu) # 右侧文件内容预览 content_frame = ttk.Frame(paned_window) paned_window.add(content_frame, weight=2) self.content_text = scrolledtext.ScrolledText( content_frame, wrap=tk.WORD, font=("Consolas", 10), padx=5, pady=5 ) self.content_text.pack(fill=tk.BOTH, expand=True) # 文本区域右键菜单 text_menu = tk.Menu(self.master, tearoff=0) text_menu.add_command(label="复制", command=self.copy_selected_text) self.content_text.bind("<Button-3>", lambda e: text_menu.tk_popup(e.x_root, e.y_root)) # 高亮标签 self.content_text.tag_configure("match", background="yellow") self.content_text.tag_configure("linenum", foreground="blue") self.content_text.tag_configure("header", foreground="darkgreen", font=("Arial", 10, "bold")) self.content_text.tag_configure("warning", foreground="red", font=("Arial", 10, "italic")) # 初始化变量 self.results = {} self.all_files = [] self.file_paths = [] self.stop_requested = False self.search_thread = None def browse_directory(self): directory = filedialog.askdirectory(title="选择搜索目录") if directory: self.dir_entry.delete(0, tk.END) self.dir_entry.insert(0, directory) def start_search(self): # 重置状态 self.progress_var.set(0) self.stop_requested = False self.results = {} self.all_files = [] self.file_paths = [] self.file_tree.delete(*self.file_tree.get_children()) self.content_text.delete(1.0, tk.END) self.status_label.config(text="正在搜索...") self.search_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.stats_label.config(text="") # 获取搜索参数 directory = self.dir_entry.get().strip() keyword = self.keyword_entry.get().strip() file_filter = self.filter_entry.get().strip() # 验证输入 if not directory or not os.path.isdir(directory): messagebox.showerror("错误", "请选择有效的搜索目录") self.search_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) return if not keyword: messagebox.showerror("错误", "请输入搜索关键词") self.search_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) return # 解析文件过滤器 if file_filter == "": filter_patterns = ['*'] else: separators = [';', '|', ' ', ','] for sep in separators: if sep in file_filter: filter_patterns = [pat.strip() for pat in file_filter.split(sep)] break else: filter_patterns = [file_filter] # 编译搜索模式 flags = re.IGNORECASE if self.case_var.get() else 0 try: if self.regex_var.get(): pattern = re.compile(keyword, flags) else: escaped_keyword = re.escape(keyword) pattern = re.compile(escaped_keyword, flags) except re.error as e: messagebox.showerror("正则表达式错误", f"无效的正则表达式: {str(e)}") self.search_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) return # 在后台线程中执行搜索 self.search_thread = threading.Thread( target=self.perform_search, args=(directory, filter_patterns, pattern), daemon=True ) self.search_thread.start() def perform_search(self, directory, filter_patterns, pattern): """在后台线程中执行文件搜索""" try: # 收集所有匹配的文件 all_files = [] for root, _, files in os.walk(directory): if self.stop_requested: self.master.after(0, lambda: self.status_label.config(text="搜索已取消")) return for file in files: file_path = os.path.join(root, file) # 检查文件大小限制(避免处理超大文件) try: file_size = os.path.getsize(file_path) if file_size > 100 * 1024 * 1024: # 100MB continue except: continue # 检查是否符合任一过滤模式 if any(fnmatch.fnmatch(file, pat) for pat in filter_patterns): all_files.append(file_path) self.all_files = all_files total_files = len(all_files) # 初始化进度条 self.master.after(0, lambda: self.progress_bar.config(maximum=total_files)) self.master.after(0, lambda: self.stats_label.config(text=f"扫描到 {total_files} 个文件")) # 搜索每个文件 self.results = {} processed = 0 matches_found = 0 for file_path in self.all_files: if self.stop_requested: break processed += 1 # 更新进度条(安全方式) self.master.after(0, lambda v=processed: self.progress_var.set(v)) if processed % 10 == 0: # 每处理10个文件更新一次进度 self.master.after(0, lambda p=processed, t=total_files: self.stats_label.config(text=f"处理中: {p}/{t} 文件 ({round(p/t*100,1)}%)")) # 忽略二进制文件(除非用户选择包含) if not self.binary_var.get() and self.is_binary(file_path): continue # 获取文件扩展名 _, ext = os.path.splitext(file_path) ext_lower = ext.lower() # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.search_in_office_file(file_path, pattern) # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.search_in_archive(file_path, pattern) # 处理文本文件 else: matches = self.search_in_text_file(file_path, pattern) if matches: self.results[file_path] = matches matches_found += len(matches) # 在UI线程中添加文件到列表 filename = os.path.basename(file_path) self.master.after(0, lambda fp=file_path, fn=filename: self.file_tree.insert("", "end", values=(fn, fp))) # 更新完成状态 if self.stop_requested: status_text = f"搜索已取消 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" else: status_text = f"搜索完成 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" self.master.after(0, lambda: self.status_label.config(text=status_text)) self.master.after(0, lambda: self.stats_label.config(text=f"已处理 {processed}/{total_files} 文件")) self.master.after(0, lambda: self.progress_var.set(total_files)) except Exception as e: # 记录详细错误日志 error_info = f"搜索错误: {type(e).__name__} - {str(e)}" print(error_info) with open("search_errors.log", "a") as log: log.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_info}\n") import traceback traceback.print_exc(file=log) self.master.after(0, lambda: messagebox.showerror( "搜索错误", f"发生严重错误: {error_info}\n详细信息已记录到日志" )) finally: self.master.after(0, lambda: self.search_button.config(state=tk.NORMAL)) self.master.after(0, lambda: self.stop_button.config(state=tk.DISABLED)) self.search_thread = None def search_in_text_file(self, filepath, pattern): """在文本文件中搜索匹配项""" matches = [] try: encoding = self.detect_encoding(filepath) try: with open(filepath, 'r', encoding=encoding, errors='replace') as f: for line_num, line in enumerate(f, 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except UnicodeDecodeError: # 特殊编码处理回退 with open(filepath, 'rb') as f: content = f.read() try: text = content.decode('utf-8', errors='replace') except: text = content.decode('latin-1', errors='replace') for line_num, line in enumerate(text.splitlines(), 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"读取文本文件失败 {filepath}: {str(e)}") return matches def search_in_office_file(self, filepath, pattern): """在Office文件中搜索文本内容""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # DOCX文件处理 if ext_lower == '.docx': doc = docx.Document(filepath) # 搜索段落 for i, para in enumerate(doc.paragraphs, 1): if para.text and pattern.search(para.text): matches.append((i, f"段落 {i}: {para.text[:100]}" + ("..." if len(para.text) > 100 else ""))) # 搜索表格 for table in doc.tables: for row_idx, row in enumerate(table.rows, 1): for cell_idx, cell in enumerate(row.cells, 1): if cell.text and pattern.search(cell.text): content = cell.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((row_idx, f"表格 行{row_idx}列{cell_idx}: {content}")) # XLSX/XLS文件处理 elif ext_lower in ('.xlsx', '.xls', '.xlsm'): # 处理新格式Excel文件 if ext_lower in ('.xlsx', '.xlsm'): wb = load_workbook(filepath, read_only=True, data_only=True) for sheet_name in wb.sheetnames: sheet = wb[sheet_name] for row_idx, row in enumerate(sheet.iter_rows(values_only=True), 1): for col_idx, cell in enumerate(row, 1): if cell is not None and pattern.search(str(cell)): cell_ref = f"{chr(64+col_idx)}{row_idx}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx, f"工作表 '{sheet_name}' 单元格 {cell_ref}: {cell_value}")) # 处理旧格式Excel文件 elif ext_lower == '.xls': wb = xlrd.open_workbook(filepath) for sheet_idx in range(wb.nsheets): sheet = wb.sheet_by_index(sheet_idx) for row_idx in range(sheet.nrows): for col_idx in range(sheet.ncols): cell = sheet.cell_value(row_idx, col_idx) if cell and pattern.search(str(cell)): cell_ref = f"{chr(65+col_idx)}{row_idx+1}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx+1, f"工作表 '{sheet.name}' 单元格 {cell_ref}: {cell_value}")) # PPTX文件处理 elif ext_lower == '.pptx': from pptx import Presentation ppt = Presentation(filepath) # 搜索幻灯片文本 for slide_idx, slide in enumerate(ppt.slides, 1): for shape in slide.shapes: if hasattr(shape, "text"): if shape.text and pattern.search(shape.text): content = shape.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((slide_idx, f"幻灯片 {slide_idx}: {content}")) # PDF文件处理 elif ext_lower == '.pdf': with open(filepath, 'rb') as f: pdf = PyPDF2.PdfReader(f) for page_num in range(len(pdf.pages)): page_text = pdf.pages[page_num].extract_text() if page_text and pattern.search(page_text): # 提取匹配内容 matches_found = [] for match in pattern.finditer(page_text): context = page_text[max(0, match.start()-20):match.end()+20] context = context.replace('\n', ' ').strip() matches_found.append(context) # 添加到结果 if matches_found: preview = "; ".join(matches_found[:3]) # 显示前3个匹配 if len(matches_found) > 3: preview += f" ... (+{len(matches_found)-3} 更多)" matches.append((page_num+1, f"页面 {page_num+1}: {preview}")) # 旧版DOC文件处理 elif ext_lower == '.doc': try: # 尝试使用antiword转换DOC为文本 result = subprocess.run(['antiword', filepath], capture_output=True, text=True, timeout=10) if result.returncode == 0: doc_text = result.stdout for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception: # 备用方法:使用python-doc处理 import win32com.client word = win32com.client.Dispatch("Word.Application") word.Visible = False doc = word.Documents.Open(filepath) doc_text = doc.Content.Text doc.Close() word.Quit() for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"处理Office文件失败 {filepath}: {str(e)}") return matches def search_in_archive(self, filepath, pattern): """在压缩文件中搜索匹配项""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # ZIP文件处理 if ext_lower in ('.zip', '.jar', '.war'): with zipfile.ZipFile(filepath, 'r') as archive: for name in archive.namelist(): # 只处理文本文件和Office文档 if not name.endswith(('/')) and not self.is_binary(name): try: with archive.open(name) as file: content = file.read(4096) # 只读取前4KB # 尝试检测编码 result = chardet.detect(content) encoding = result['encoding'] if result['confidence'] > 0.7 else 'utf-8' # 解码内容并搜索 try: text_content = content.decode(encoding, errors='replace') if pattern.search(text_content): matches.append((name, f"压缩文件中的文件: {name}")) except: # 二进制内容搜索 if pattern.search(content): matches.append((name, f"压缩文件中的文件(二进制内容): {name}")) except Exception: continue # 其他压缩格式(需要外部工具) elif ext_lower in ('.rar', '.7z', '.tar', '.gz'): # 使用7zip命令行工具解压并搜索 temp_dir = tempfile.mkdtemp() try: subprocess.run(['7z', 'x', filepath, f'-o{temp_dir}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=60) # 递归搜索解压的目录 for root, _, files in os.walk(temp_dir): for file in files: full_path = os.path.join(root, file) _, file_ext = os.path.splitext(file) file_ext = file_ext.lower() # 只在文本/Office文件中搜索 if file_ext in ['', '.txt', '.py', '.java', '.c', '.cpp', '.h', '.html', '.xml', '.json', '.csv', '.docx', '.xlsx', '.pptx', '.pdf']: if file_ext in ['.docx', '.xlsx', '.pptx', '.pdf']: file_matches = self.search_in_office_file(full_path, pattern) else: file_matches = self.search_in_text_file(full_path, pattern) if file_matches: matches.append((file, f"压缩文件中的文件: {file}")) finally: shutil.rmtree(temp_dir, ignore_errors=True) except Exception as e: print(f"处理压缩文件失败 {filepath}: {str(e)}") return matches def detect_encoding(self, filepath): """改进的文件编码检测方法""" try: # 尝试读取文件前4KB进行编码检测 with open(filepath, 'rb') as f: raw_data = f.read(4096) # 使用chardet进行编码检测 result = chardet.detect(raw_data) # 优先使用检测到的编码,否则尝试常见编码 if result['confidence'] > 0.7: return result['encoding'] # 中文环境常用编码回退策略 common_encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1'] for encoding in common_encodings: try: # 尝试解码验证 raw_data.decode(encoding, errors='strict') return encoding except UnicodeDecodeError: continue # 默认使用UTF-8 return 'utf-8' except Exception: return 'utf-8' def is_binary(self, filepath): """检查文件是否为二进制文件""" try: with open(filepath, 'rb') as f: chunk = f.read(1024) if b'\0' in chunk: # 空字节是二进制文件的标志 return True # 检查高字节值 if any(byte >= 0x80 for byte in chunk): return True return False except: return False def stop_search(self): """停止当前搜索""" self.stop_requested = True self.status_label.config(text="正在停止搜索...") self.stop_button.config(state=tk.DISABLED) # 冻结进度条显示当前进度 self.progress_bar.config(mode='indeterminate' if self.progress_var.get() == 0 else 'determinate') def export_results(self): """导出搜索结果""" if not self.results: messagebox.showinfo("导出结果", "没有可导出的搜索结果") return file_path = filedialog.asksaveasfilename( title="保存搜索结果为", defaultextension=".csv", filetypes=[("CSV 文件", "*.csv"), ("文本文件", "*.txt")] ) if not file_path: return try: with open(file_path, 'w', encoding='utf-8') as f: # 写出CSV头部 f.write("文件路径,匹配行号,匹配内容\n") # 写出每项结果 for file, matches in self.results.items(): for line_num, match_content in matches: # 清理内容中的逗号 cleaned_content = match_content.replace('"', '""').replace(',', ';') f.write(f'"{file}",{line_num},"{cleaned_content}"\n') messagebox.showinfo("导出成功", f"搜索结果已保存到:\n{file_path}") except Exception as e: messagebox.showerror("导出错误", f"导出失败: {str(e)}") def show_file_content(self, event=None): """在预览区域显示文件内容""" # 获取选中的文件 selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] # 清空预览区域 self.content_text.delete(1.0, tk.END) # 获取文件扩展名 _, ext = os.path.splitext(filepath) ext_lower = ext.lower() # 显示文件路径标题 self.content_text.insert(tk.END, f"文件路径: {filepath}\n", "header") # 处理不同文件类型 try: # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.results.get(filepath, []) if not matches: self.content_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.content_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") # 显示每个匹配项 for i, (line_num, content) in enumerate(matches, 1): self.content_text.insert(tk.END, f"[匹配项 {i}] 位置: {line_num}\n") self.content_text.insert(tk.END, f"{content}\n\n") # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.results.get(filepath, []) if not matches: self.content_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.content_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") for i, (file_in_zip, content) in enumerate(matches, 1): self.content_text.insert(tk.END, f"[匹配项 {i}] 文件: {file_in_zip}\n") self.content_text.insert(tk.END, f"{content}\n\n") # 处理文本文件 else: # 获取关键词高亮模式 keyword = self.keyword_entry.get().strip() flags = re.IGNORECASE if self.case_var.get() else 0 if self.regex_var.get(): try: pattern = re.compile(keyword, flags) except: pattern = None else: pattern = re.compile(re.escape(keyword), flags) # 显示文件内容并高亮匹配 self.content_text.insert(tk.END, "\n文件内容:\n\n", "header") # 限制预览内容大小(最多显示1000行) max_preview_lines = 1000 try: encoding = self.detect_encoding(filepath) with open(filepath, 'r', encoding=encoding, errors='replace') as f: line_count = 0 for line in f: line_count += 1 if line_count > max_preview_lines: self.content_text.insert(tk.END, f"\n... (文件过大,仅显示前{max_preview_lines}行)\n", "warning") break # 插入行号 self.content_text.insert(tk.END, f"{line_count:4d} | ", "linenum") # 插入行内容并高亮匹配 if pattern: start_idx = 0 for match in pattern.finditer(line): # 插入匹配前的文本 self.content_text.insert(tk.END, line[start_idx:match.start()]) # 插入高亮的匹配文本 self.content_text.insert(tk.END, match.group(), "match") start_idx = match.end() # 插入匹配后的文本 self.content_text.insert(tk.END, line[start_idx:]) else: self.content_text.insert(tk.END, line) except UnicodeDecodeError: self.content_text.insert(tk.END, "\n无法解码此文件内容(可能是二进制文件)\n", "warning") except Exception as e: self.content_text.insert(tk.END, f"\n读取文件时出错: {str(e)}\n", "warning") except Exception as e: self.content_text.insert(tk.END, f"\n加载文件内容出错: {str(e)}\n", "warning") def open_selected_file(self, event=None): """用系统默认程序打开选中的文件""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] try: if sys.platform == "win32": os.startfile(filepath) elif sys.platform == "darwin": # macOS subprocess.run(["open", filepath]) else: # Linux subprocess.run(["xdg-open", filepath]) except Exception as e: messagebox.showerror("打开文件失败", f"无法打开文件: {str(e)}") def open_file_location(self): """在文件资源管理器中打开文件所在位置""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] folder = os.path.dirname(filepath) try: if sys.platform == "win32": subprocess.run(["explorer", "/select,", filepath]) elif sys.platform == "darwin": # macOS subprocess.run(["open", "-R", filepath]) else: # Linux subprocess.run(["xdg-open", folder]) except Exception as e: messagebox.showerror("打开位置失败", f"无法打开位置: {str(e)}") def show_file_context_menu(self, event): """显示文件列表的右键菜单""" item = self.file_tree.identify_row(event.y) if item: self.file_tree.selection_set(item) self.file_menu.tk_popup(event.x_root, event.y_root) def copy_selected_text(self): """复制预览区域中选中的文本""" selected_text = self.content_text.get(tk.SEL_FIRST, tk.SEL_LAST) if selected_text: self.master.clipboard_clear() self.master.clipboard_append(selected_text) # 程序入口 if __name__ == "__main__": root = tk.Tk() app = FileSearchApp(root) # 添加图标(如果有) try: if sys.platform == "win32": root.iconbitmap("search_icon.ico") else: img = tk.PhotoImage(file="search_icon.png") root.iconphoto(True, img) except: pass root.mainloop() 检查问题,搜索出错

filetype

# -*- coding: utf-8 -*- import os import re import sys import time import threading import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext from tkinter.font import Font import fnmatch import subprocess import shutil import docx from openpyxl import load_workbook import PyPDF2 import zipfile import chardet import xlrd import tempfile class EnhancedFileSearchApp: def __init__(self, master): self.master = master master.title("🎯 高级文件搜索工具") master.geometry("1200x800") master.minsize(1000, 700) # 设置现代主题和颜色方案 theme = "vista" if sys.platform == "win32" else "aqua" self.style = ttk.Style() self.style.theme_use(theme) # 自定义配色方案 self.colors = { "bg": "#f5f6fa", "header": "#3498db", "accent": "#2980b9", "warning": "#e74c3c", "success": "#2ecc71", "text": "#2c3e50", "highlight": "#f1c40f" } # 设置主窗口背景色 master.configure(bg=self.colors["bg"]) # 创建主框架 main_frame = ttk.Frame(master, padding=(15, 15)) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 创建搜索面板(顶部) search_frame = ttk.LabelFrame( main_frame, text="⚙️ 搜索选项", padding=(15, 10), style="Search.TLabelframe" ) search_frame.pack(side=tk.TOP, fill=tk.X, padx=0, pady=(0, 10)) # 修改为TOP布局 # 配置搜索面板样式 self.style.configure("Search.TLabelframe", background=self.colors["bg"], bordercolor=self.colors["accent"], lightcolor=self.colors["accent"]) self.style.configure("Search.TLabelframe.Label", font=("Arial", 10, "bold"), foreground=self.colors["header"]) # 搜索目录 dir_frame = ttk.Frame(search_frame) dir_frame.pack(fill=tk.X, pady=(0, 10)) ttk.Label(dir_frame, text="📁 搜索目录:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.dir_entry = ttk.Entry(dir_frame, width=35) self.dir_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) self.dir_entry.insert(0, os.getcwd()) browse_btn = ttk.Button( dir_frame, text="浏览...", command=self.browse_directory, style="Accent.TButton" ) browse_btn.pack(side=tk.RIGHT) # 关键词 kw_frame = ttk.Frame(search_frame) kw_frame.pack(fill=tk.X, pady=5) ttk.Label(kw_frame, text="🔍 关键词:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.keyword_entry = ttk.Entry(kw_frame, width=40) self.keyword_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) # 文件过滤 filter_frame = ttk.Frame(search_frame) filter_frame.pack(fill=tk.X, pady=5) ttk.Label(filter_frame, text="📄 文件过滤:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.filter_entry = ttk.Entry(filter_frame, width=40) self.filter_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) self.filter_entry.insert(0, "*.c;*.h;*.prm;*.xlsx;*.xls;*.doc;*.docx;*.pdf") # 搜索选项 options_frame = ttk.Frame(search_frame) options_frame.pack(fill=tk.X, pady=10) self.case_var = tk.BooleanVar(value=False) case_chk = ttk.Checkbutton( options_frame, text="忽略大小写", variable=self.case_var, style="Custom.TCheckbutton" ) case_chk.pack(side=tk.LEFT, padx=(0, 15)) self.regex_var = tk.BooleanVar(value=False) regex_chk = ttk.Checkbutton( options_frame, text="正则表达式", variable=self.regex_var, style="Custom.TCheckbutton" ) regex_chk.pack(side=tk.LEFT, padx=(0, 15)) self.binary_var = tk.BooleanVar(value=False) binary_chk = ttk.Checkbutton( options_frame, text="包含二进制", variable=self.binary_var, style="Custom.TCheckbutton" ) binary_chk.pack(side=tk.LEFT, padx=(0, 15)) self.highlight_var = tk.BooleanVar(value=True) highlight_chk = ttk.Checkbutton( options_frame, text="关键字高亮", variable=self.highlight_var, style="Custom.TCheckbutton" ) highlight_chk.pack(side=tk.LEFT) # 配置选项样式 self.style.configure("Custom.TCheckbutton", font=("Arial", 9), foreground=self.colors["text"]) # 按钮组 btn_frame = ttk.Frame(search_frame) btn_frame.pack(fill=tk.X, pady=(15, 5)) self.search_btn = ttk.Button( btn_frame, text="🔎 开始搜索", command=self.start_search, style="Accent.TButton" ) self.search_btn.pack(side=tk.LEFT, padx=(0, 10)) self.stop_btn = ttk.Button( btn_frame, text="⏹️ 停止", command=self.stop_search, style="Warning.TButton", state=tk.DISABLED ) self.stop_btn.pack(side=tk.LEFT, padx=(0, 10)) self.export_btn = ttk.Button( btn_frame, text="💾 导出结果", command=self.export_results, style="Success.TButton" ) self.export_btn.pack(side=tk.LEFT) # 按钮样式 self.style.configure("Accent.TButton", font=("Arial", 10, "bold"), foreground="white", background=self.colors["accent"]) self.style.configure("Warning.TButton", foreground="white", background=self.colors["warning"]) self.style.configure("Success.TButton", foreground="white", background=self.colors["success"]) # 状态栏 status_frame = ttk.Frame(search_frame) status_frame.pack(fill=tk.X, pady=(10, 0)) self.status_label = ttk.Label( status_frame, text="🟢 就绪", foreground=self.colors["success"], font=("Arial", 9, "bold") ) self.status_label.pack(side=tk.LEFT) self.progress = ttk.Progressbar( status_frame, orient="horizontal", length=100, mode="determinate", style="Custom.Horizontal.TProgressbar" ) self.progress.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) # 进度条样式 self.style.configure("Custom.Horizontal.TProgressbar", thickness=20, background=self.colors["accent"], troughcolor=self.colors["bg"]) self.stats_label = ttk.Label( status_frame, text="", foreground=self.colors["text"], font=("Arial", 9) ) self.stats_label.pack(side=tk.RIGHT) # 创建结果面板 results_frame = ttk.Frame(main_frame) results_frame.pack(fill=tk.BOTH, expand=True) # 创建结果面板(在搜索面板下方) results_frame = ttk.Frame(main_frame) results_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=10) # 修改为TOP布局 # 分割窗格(文件列表和预览左右排列) pane = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) pane.pack(fill=tk.BOTH, expand=True) # 文件列表(左侧) file_frame = ttk.LabelFrame(pane, text="📂 搜索结果", padding=10) pane.add(file_frame, weight=1) # 文件预览(右侧) preview_frame = ttk.LabelFrame(pane, text="🔍 预览内容", padding=10) pane.add(preview_frame, weight=2) # 创建Treeview显示文件列表 file_tree_frame = ttk.Frame(file_frame) file_tree_frame.pack(fill=tk.BOTH, expand=True) # 定义树状视图列时添加fullpath列 columns = ("filename", "size", "modified", "fullpath") self.file_tree = ttk.Treeview( file_tree_frame, columns=columns, show="headings", selectmode="browse" ) # 配置列时隐藏fullpath列 self.file_tree.column("fullpath", width=0, stretch=False) # 隐藏列 # 配置Treeview样式 self.style.configure("Custom.Treeview", font=("Arial", 9), rowheight=25) self.style.configure("Custom.Treeview.Heading", font=("Arial", 9, "bold"), background=self.colors["accent"], foreground="white") # 配置列 self.file_tree.heading("filename", text="文件名", anchor="w") self.file_tree.heading("size", text="大小", anchor="center") self.file_tree.heading("modified", text="修改时间", anchor="w") self.file_tree.column("filename", width=250, anchor="w") self.file_tree.column("size", width=80, anchor="center") self.file_tree.column("modified", width=150, anchor="w") scroll_y = ttk.Scrollbar(file_tree_frame, orient="vertical", command=self.file_tree.yview) scroll_x = ttk.Scrollbar(file_tree_frame, orient="horizontal", command=self.file_tree.xview) self.file_tree.configure(yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set) self.file_tree.grid(row=0, column=0, sticky="nsew") scroll_y.grid(row=0, column=1, sticky="ns") scroll_x.grid(row=1, column=0, sticky="ew") file_tree_frame.rowconfigure(0, weight=1) file_tree_frame.columnconfigure(0, weight=1) self.preview_text = scrolledtext.ScrolledText( preview_frame, wrap=tk.WORD, font=("Consolas", 10), padx=10, pady=10, bg="#FFFFFF", fg=self.colors["text"], relief="flat" ) self.preview_text.pack(fill=tk.BOTH, expand=True) # 配置高亮标签 self.preview_text.tag_configure("highlight", background=self.colors["highlight"]) self.preview_text.tag_configure("match", background="#c8e6c9") self.preview_text.tag_configure("header", foreground=self.colors["success"], font=("Arial", 10, "bold")) self.preview_text.tag_configure("warning", foreground=self.colors["warning"], font=("Arial", 9, "italic")) self.preview_text.tag_configure("linenum", foreground="#7f8c8d") # 初始化变量 self.search_thread = None self.stop_requested = False self.results = {} self.all_files = [] # 绑定事件 self.file_tree.bind("<<TreeviewSelect>>", self.show_file_content) self.file_tree.bind('<Double-1>', self.open_selected_file) # 配置网格布局 results_frame.columnconfigure(0, weight=1) results_frame.rowconfigure(0, weight=1) def browse_directory(self): """浏览目录""" directory = filedialog.askdirectory(title="选择搜索目录") if directory: self.dir_entry.delete(0, tk.END) self.dir_entry.insert(0, directory) def start_search(self): """开始搜索""" # 重置状态 self.progress["value"] = 0 self.stop_requested = False self.results = {} self.all_files = [] # 清空UI for item in self.file_tree.get_children(): self.file_tree.delete(item) self.preview_text.delete("1.0", tk.END) # 获取参数 directory = self.dir_entry.get().strip() keyword = self.keyword_entry.get().strip() file_filter = self.filter_entry.get().strip() # 验证输入 if not directory or not os.path.isdir(directory): messagebox.showerror("错误", "请选择有效的搜索目录") return if not keyword: messagebox.showerror("错误", "请输入搜索关键词") return self.status_label.config(text="🟡 正在搜索...", foreground=self.colors["warning"]) self.search_btn.config(state=tk.DISABLED) self.stop_btn.config(state=tk.NORMAL) # 解析文件过滤器 filter_patterns = [pat.strip() for pat in file_filter.split(';')] if ';' in file_filter else [file_filter] # 编译搜索模式 flags = re.IGNORECASE if self.case_var.get() else 0 try: if self.regex_var.get(): pattern = re.compile(keyword, flags) else: pattern = re.compile(re.escape(keyword), flags) except re.error as e: messagebox.showerror("正则表达式错误", f"无效的正则表达式: {str(e)}") self.search_btn.config(state=tk.NORMAL) self.stop_btn.config(state=tk.DISABLED) return # 在后台线程搜索 self.search_thread = threading.Thread( target=self.perform_search, args=(directory, filter_patterns, pattern), daemon=True ) self.search_thread.start() def perform_search(self, directory, filter_patterns, pattern): """执行文件搜索""" try: # 收集所有匹配的文件 all_files = [] for root, _, files in os.walk(directory): if self.stop_requested: self.master.after(0, lambda: self.status_label.config( text="🟠 搜索已取消", foreground=self.colors["warning"])) return for file in files: file_path = os.path.join(root, file) # 检查文件大小限制(避免处理超大文件) try: file_size = os.path.getsize(file_path) limit = 100 * 1024 * 1024 # 100MB if file_size > limit: continue except: continue # 检查是否符合任一过滤模式 if any(fnmatch.fnmatch(file, pat) for pat in filter_patterns): all_files.append(file_path) self.all_files = all_files total_files = len(all_files) # 更新UI self.master.after(0, lambda: self.progress.configure(maximum=total_files)) self.master.after(0, lambda: self.stats_label.config(text=f"0/{total_files}")) # 搜索每个文件 self.results = {} processed = 0 matches_found = 0 for file_path in all_files: if self.stop_requested: break processed += 1 # 更新进度 self.master.after(0, lambda v=processed: self.progress.configure(value=v)) if processed % 10 == 0: # 每处理10个文件更新一次进度 self.master.after(0, lambda p=processed, t=total_files: self.stats_label.config(text=f"{p}/{t} ({p/t*100:.1f}%)")) # 忽略二进制文件(除非用户选择包含) if not self.binary_var.get() and self.is_binary(file_path): continue # 获取文件扩展名 _, ext = os.path.splitext(file_path) ext_lower = ext.lower() # 处理不同文件类型 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.search_in_office_file(file_path, pattern) elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.search_in_archive(file_path, pattern) else: matches = self.search_in_text_file(file_path, pattern) if matches: self.results[file_path] = matches matches_found += len(matches) # 获取文件信息 file_name = os.path.basename(file_path) try: size = os.path.getsize(file_path) size_str = f"{size/1024:.1f}KB" if size < 1024*1024 else f"{size/(1024*1024):.1f}MB" mod_time = time.ctime(os.path.getmtime(file_path)) except: size_str = "N/A" mod_time = "N/A" # 在UI线程中添加文件到列表 self.master.after(0, lambda fp=file_path, fn=file_name, sz=size_str, mt=mod_time: self.file_tree.insert("", "end", values=(fn, sz, mt, fp))) # 更新完成状态 if self.stop_requested: status_text = f"🟠 搜索已取消 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" else: status_text = f"🟢 搜索完成 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" self.master.after(0, lambda: self.status_label.config(text=status_text, foreground=self.colors["success"])) self.master.after(0, lambda: self.stats_label.config(text=f"已处理 {processed}/{total_files} 文件")) self.master.after(0, lambda: self.progress.configure(value=total_files)) except Exception as e: # 记录详细错误日志 error_info = f"搜索错误: {type(e).__name__} - {str(e)}" print(error_info) with open("search_errors.log", "a") as log: log.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_info}\n") import traceback traceback.print_exc(file=log) self.master.after(0, lambda: messagebox.showerror( "搜索错误", f"发生严重错误: {error_info}\n详细信息已记录到日志" )) finally: self.master.after(0, lambda: self.search_btn.config(state=tk.NORMAL)) self.master.after(0, lambda: self.stop_btn.config(state=tk.DISABLED)) self.search_thread = None def search_in_text_file(self, filepath, pattern): """在文本文件中搜索匹配项""" matches = [] try: encoding = self.detect_encoding(filepath) try: with open(filepath, 'r', encoding=encoding, errors='replace') as f: for line_num, line in enumerate(f, 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except UnicodeDecodeError: # 特殊编码处理回退 with open(filepath, 'rb') as f: content = f.read() try: text = content.decode('utf-8', errors='replace') except: text = content.decode('latin-1', errors='replace') for line_num, line in enumerate(text.splitlines(), 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"读取文本文件失败 {filepath}: {str(e)}") return matches def search_in_office_file(self, filepath, pattern): """在Office文件中搜索文本内容""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # DOCX文件处理 if ext_lower == '.docx': doc = docx.Document(filepath) # 搜索段落 for i, para in enumerate(doc.paragraphs, 1): if para.text and pattern.search(para.text): matches.append((i, f"段落 {i}: {para.text[:100]}" + ("..." if len(para.text) > 100 else ""))) # 搜索表格 for table in doc.tables: for row_idx, row in enumerate(table.rows, 1): for cell_idx, cell in enumerate(row.cells, 1): if cell.text and pattern.search(cell.text): content = cell.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((row_idx, f"表格 行{row_idx}列{cell_idx}: {content}")) # XLSX/XLS文件处理 elif ext_lower in ('.xlsx', '.xls', '.xlsm'): # 处理新格式Excel文件 if ext_lower in ('.xlsx', '.xlsm'): wb = load_workbook(filepath, read_only=True, data_only=True) for sheet_name in wb.sheetnames: sheet = wb[sheet_name] for row_idx, row in enumerate(sheet.iter_rows(values_only=True), 1): for col_idx, cell in enumerate(row, 1): if cell is not None and pattern.search(str(cell)): cell_ref = f"{chr(64+col_idx)}{row_idx}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx, f"工作表 '{sheet_name}' 单元格 {cell_ref}: {cell_value}")) # 处理旧格式Excel文件 elif ext_lower == '.xls': wb = xlrd.open_workbook(filepath) for sheet_idx in range(wb.nsheets): sheet = wb.sheet_by_index(sheet_idx) for row_idx in range(sheet.nrows): for col_idx in range(sheet.ncols): cell = sheet.cell_value(row_idx, col_idx) if cell and pattern.search(str(cell)): cell_ref = f"{chr(65+col_idx)}{row_idx+1}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx+1, f"工作表 '{sheet.name}' 单元格 {cell_ref}: {cell_value}")) # PPTX文件处理 elif ext_lower == '.pptx': from pptx import Presentation ppt = Presentation(filepath) # 搜索幻灯片文本 for slide_idx, slide in enumerate(ppt.slides, 1): for shape in slide.shapes: if hasattr(shape, "text"): if shape.text and pattern.search(shape.text): content = shape.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((slide_idx, f"幻灯片 {slide_idx}: {content}")) # PDF文件处理 elif ext_lower == '.pdf': with open(filepath, 'rb') as f: pdf = PyPDF2.PdfReader(f) for page_num in range(len(pdf.pages)): page_text = pdf.pages[page_num].extract_text() if page_text and pattern.search(page_text): # 提取匹配内容 matches_found = [] for match in pattern.finditer(page_text): context = page_text[max(0, match.start()-20):match.end()+20] context = context.replace('\n', ' ').strip() matches_found.append(context) # 添加到结果 if matches_found: preview = "; ".join(matches_found[:3]) # 显示前3个匹配 if len(matches_found) > 3: preview += f" ... (+{len(matches_found)-3} 更多)" matches.append((page_num+1, f"页面 {page_num+1}: {preview}")) # 旧版DOC文件处理 elif ext_lower == '.doc': try: # 尝试使用antiword转换DOC为文本 result = subprocess.run(['antiword', filepath], capture_output=True, text=True, timeout=10) if result.returncode == 0: doc_text = result.stdout for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception: # 备用方法:使用python-doc处理 import win32com.client word = win32com.client.Dispatch("Word.Application") word.Visible = False doc = word.Documents.Open(filepath) doc_text = doc.Content.Text doc.Close() word.Quit() for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"处理Office文件失败 {filepath}: {str(e)}") return matches def search_in_archive(self, filepath, pattern): """在压缩文件中搜索匹配项""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # ZIP文件处理 if ext_lower in ('.zip', '.jar', '.war'): with zipfile.ZipFile(filepath, 'r') as archive: for name in archive.namelist(): # 只处理文本文件和Office文档 if not name.endswith(('/')) and not self.is_binary(name): try: with archive.open(name) as file: content = file.read(4096) # 只读取前4KB # 尝试检测编码 result = chardet.detect(content) encoding = result['encoding'] if result['confidence'] > 0.7 else 'utf-8' # 解码内容并搜索 try: text_content = content.decode(encoding, errors='replace') if pattern.search(text_content): matches.append((name, f"压缩文件中的文件: {name}")) except: # 二进制内容搜索 if pattern.search(content): matches.append((name, f"压缩文件中的文件(二进制内容): {name}")) except Exception: continue # 其他压缩格式(需要外部工具) elif ext_lower in ('.rar', '.7z', '.tar', '.gz'): # 使用7zip命令行工具解压并搜索 temp_dir = tempfile.mkdtemp() try: subprocess.run(['7z', 'x', filepath, f'-o{temp_dir}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=60) # 递归搜索解压的目录 for root, _, files in os.walk(temp_dir): for file in files: full_path = os.path.join(root, file) _, file_ext = os.path.splitext(file) file_ext = file_ext.lower() # 只在文本/Office文件中搜索 if file_ext in ['', '.txt', '.py', '.java', '.c', '.cpp', '.h', '.html', '.xml', '.json', '.csv', '.docx', '.xlsx', '.pptx', '.pdf']: if file_ext in ['.docx', '.xlsx', '.pptx', '.pdf']: file_matches = self.search_in_office_file(full_path, pattern) else: file_matches = self.search_in_text_file(full_path, pattern) if file_matches: matches.append((file, f"压缩文件中的文件: {file}")) finally: shutil.rmtree(temp_dir, ignore_errors=True) except Exception as e: print(f"处理压缩文件失败 {filepath}: {str(e)}") return matches def detect_encoding(self, filepath): """改进的文件编码检测方法""" try: # 尝试读取文件前4KB进行编码检测 with open(filepath, 'rb') as f: raw_data = f.read(4096) # 使用chardet进行编码检测 result = chardet.detect(raw_data) # 优先使用检测到的编码,否则尝试常见编码 if result['confidence'] > 0.7: return result['encoding'] # 中文环境常用编码回退策略 common_encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1'] for encoding in common_encodings: try: # 尝试解码验证 raw_data.decode(encoding, errors='strict') return encoding except UnicodeDecodeError: continue # 默认使用UTF-8 return 'utf-8' except Exception: return 'utf-8' def is_binary(self, filepath): """检查文件是否为二进制文件""" try: with open(filepath, 'rb') as f: chunk = f.read(1024) if b'\0' in chunk: # 空字节是二进制文件的标志 return True # 检查高字节值 if any(byte >= 0x80 for byte in chunk): return True return False except: return False def stop_search(self): """停止当前搜索""" self.stop_requested = True self.status_label.config(text="🟠 正在停止...", foreground=self.colors["warning"]) self.stop_btn.config(state=tk.DISABLED) def export_results(self): """导出搜索结果""" if not self.results: messagebox.showinfo("导出结果", "没有可导出的搜索结果") return file_path = filedialog.asksaveasfilename( title="保存搜索结果为", defaultextension=".csv", filetypes=[("CSV 文件", "*.csv"), ("文本文件", "*.txt")] ) if not file_path: return try: with open(file_path, 'w', encoding='utf-8') as f: # 写出CSV头部 f.write("文件路径,匹配行号,匹配内容\n") # 写出每项结果 for file, matches in self.results.items(): for line_num, match_content in matches: # 清理内容中的逗号 cleaned_content = match_content.replace('"', '""').replace(',', ';') f.write(f'"{file}",{line_num},"{cleaned_content}"\n') messagebox.showinfo("导出成功", f"搜索结果已保存到:\n{file_path}") except Exception as e: messagebox.showerror("导出错误", f"导出失败: {str(e)}") def show_file_content(self, event=None): """在预览区域显示文件内容""" # 获取选中的文件 selection = self.file_tree.selection() if not selection: return # 获取完整文件路径 selected_item = self.file_tree.selection()[0] filepath = self.file_tree.item(selected_item, "values")[3] # 索引3是完整路径 # 清空预览区域 self.preview_text.delete(1.0, tk.END) # 获取文件扩展名 _, ext = os.path.splitext(filepath) ext_lower = ext.lower() # 显示文件路径标题 self.preview_text.insert(tk.END, f"文件路径: {filepath}\n", "header") # 处理不同文件类型 try: # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") # 显示每个匹配项 for i, (line_num, content) in enumerate(matches, 1): self.preview_text.insert(tk.END, f"[匹配项 {i}] 位置: {line_num}\n") self.preview_text.insert(tk.END, f"{content}\n\n") # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") for i, (file_in_zip, content) in enumerate(matches, 1): self.preview_text.insert(tk.END, f"[匹配项 {i}] 文件: {file_in_zip}\n") self.preview_text.insert(tk.END, f"{content}\n\n") # 处理文本文件 else: # 获取关键词高亮模式 keyword = self.keyword_entry.get().strip() flags = re.IGNORECASE if self.case_var.get() else 0 if self.regex_var.get(): try: pattern = re.compile(keyword, flags) except: pattern = None else: pattern = re.compile(re.escape(keyword), flags) # 显示文件内容并高亮匹配 self.preview_text.insert(tk.END, "\n文件内容:\n\n", "header") # 限制预览内容大小(最多显示1000行) max_preview_lines = 1000 try: encoding = self.detect_encoding(filepath) with open(filepath, 'r', encoding=encoding, errors='replace') as f: line_count = 0 for line in f: line_count += 1 if line_count > max_preview_lines: self.preview_text.insert(tk.END, f"\n... (文件过大,仅显示前{max_preview_lines}行)\n", "warning") break # 插入行号 self.preview_text.insert(tk.END, f"{line_count:4d} | ", "linenum") # 插入行内容并高亮匹配 if pattern: start_idx = 0 for match in pattern.finditer(line): # 插入匹配前的文本 self.preview_text.insert(tk.END, line[start_idx:match.start()]) # 插入高亮的匹配文本 self.preview_text.insert(tk.END, match.group(), "match") start_idx = match.end() # 插入匹配后的文本 self.preview_text.insert(tk.END, line[start_idx:]) else: self.preview_text.insert(tk.END, line) except UnicodeDecodeError: self.preview_text.insert(tk.END, "\n无法解码此文件内容(可能是二进制文件)\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n读取文件时出错: {str(e)}\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n加载文件内容出错: {str(e)}\n", "warning") def open_selected_file(self, event=None): """用系统默认程序打开选中的文件""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, "values")[3] try: if sys.platform == "win32": os.startfile(filepath) elif sys.platform == "darwin": # macOS subprocess.run(["open", filepath]) else: # Linux subprocess.run(["xdg-open", filepath]) except Exception as e: messagebox.showerror("打开文件失败", f"无法打开文件: {str(e)}") def open_file_location(self): """在文件资源管理器中打开文件所在位置""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] folder = os.path.dirname(filepath) try: if sys.platform == "win32": subprocess.run(["explorer", "/select,", filepath]) elif sys.platform == "darwin": # macOS subprocess.run(["open", "-R", filepath]) else: # Linux subprocess.run(["xdg-open", folder]) except Exception as e: messagebox.showerror("打开位置失败", f"无法打开位置: {str(e)}") def show_file_context_menu(self, event): """显示文件列表的右键菜单""" item = self.file_tree.identify_row(event.y) if item: self.file_tree.selection_set(item) self.file_menu.tk_popup(event.x_root, event.y_root) def copy_selected_text(self): """复制预览区域中选中的文本""" selected_text = self.preview_text.get(tk.SEL_FIRST, tk.SEL_LAST) if selected_text: self.master.clipboard_clear() self.master.clipboard_append(selected_text) # 程序入口 if __name__ == "__main__": root = tk.Tk() app = EnhancedFileSearchApp(root) # 添加图标(如果有) try: if sys.platform == "win32": root.iconbitmap("search_icon.ico") else: img = tk.PhotoImage(file="search_icon.png") root.iconphoto(True, img) except: pass root.mainloop() 搜索结果与搜索选项之间的间隔太大了,特别是我全屏的时候,中间空出一大片

filetype

root@hi3798mv300:~# adb version -bash: adb: command not found root@hi3798mv300:~# # 更新软件源 root@hi3798mv300:~# sudo apt update Hit:1 http://repo.huaweicloud.com/ubuntu-ports focal InRelease Hit:2 http://repo.huaweicloud.com/ubuntu-ports focal-updates InRelease Hit:3 http://repo.huaweicloud.com/ubuntu-ports focal-backports InRelease Hit:4 http://repo.huaweicloud.com/ubuntu-ports focal-security InRelease Hit:5 https://repo.huaweicloud.com/docker-ce/linux/ubuntu focal InRelease Hit:6 https://www.ecoo.top/update/repo/arm64 histb InRelease Reading package lists... Done Building dependency tree Reading state information... Done All packages are up to date. root@hi3798mv300:~# root@hi3798mv300:~# # 安装ADB依赖库 root@hi3798mv300:~# sudo apt install android-tools-adb android-tools-fastboot libusb-1.0-0 Reading package lists... Done Building dependency tree Reading state information... Done libusb-1.0-0 is already the newest version (2:1.0.23-2build1). libusb-1.0-0 set to manually installed. The following additional packages will be installed: adb android-libadb android-libbacktrace android-libbase android-libboringssl android-libcrypto-utils android-libcutils android-libetc1 android-libf2fs-utils android-liblog android-libsparse android-libunwind android-libutils android-libziparchive android-sdk-platform-tools android-sdk-platform-tools-common dmtracedump etc1tool f2fs-tools fastboot fontconfig fonts-liberation graphviz hprof-conv libann0 libcairo2 libcdt5 libcgraph6 libdatrie1 libf2fs-format4 libf2fs5 libgraphite2-3 libgts-0.7-5 libgts-bin libgvc6 libgvpr2 libharfbuzz0b libice6 liblab-gamut1 libpango-1.0-0 libpangocairo-1.0-0 libpangoft2-1.0-0 libpathplan4 libpixman-1-0 libsm6 libthai-data libthai0 libxaw7 libxcb-render0 libxcb-shm0 libxmu6 libxrender1 libxt6 p7zip p7zip-full sqlite3 x11-common Suggested packages: gsfonts graphviz-doc p7zip-rar sqlite3-doc The following NEW packages will be installed: adb android-libadb android-libbacktrace android-libbase android-libboringssl android-libcrypto-utils android-libcutils android-libetc1 android-libf2fs-utils android-liblog android-libsparse android-libunwind android-libutils android-libziparchive android-sdk-platform-tools android-sdk-platform-tools-common android-tools-adb android-tools-fastboot dmtracedump etc1tool f2fs-tools fastboot fontconfig fonts-liberation graphviz hprof-conv libann0 libcairo2 libcdt5 libcgraph6 libdatrie1 libf2fs-format4 libf2fs5 libgraphite2-3 libgts-0.7-5 libgts-bin libgvc6 libgvpr2 libharfbuzz0b libice6 liblab-gamut1 libpango-1.0-0 libpangocairo-1.0-0 libpangoft2-1.0-0 libpathplan4 libpixman-1-0 libsm6 libthai-data libthai0 libxaw7 libxcb-render0 libxcb-shm0 libxmu6 libxrender1 libxt6 p7zip p7zip-full sqlite3 x11-common 0 upgraded, 59 newly installed, 0 to remove and 0 not upgraded. Need to get 8,012 kB of archives. After this operation, 29.5 MB of additional disk space will be used. Do you want to continue? [Y/n] y Abort. root@hi3798mv300:~# adb version -bash: adb: command not found root@hi3798mv300:~# 没有啊 是安装错了吗

朱moyimi
  • 粉丝: 106
上传资源 快速赚钱