在 Linux 系統中,若要通過已修改的文件找到修改該文件的進程 PID,可以結合以下方法分析,具體取決于文件是否仍被進程打開或已被刪除但句柄仍存在:
一、文件仍被進程打開(未刪除)
如果文件當前正在被某個進程修改且未關閉/刪除,可通過以下方式查找:
1. lsof
命令(推薦)
lsof
(List Open Files)可列出系統中所有打開的文件及對應的進程信息。
命令格式:
lsof <文件路徑>
示例:
lsof /var/log/syslog
輸出解讀:
PID
列:修改文件的進程 PID。USER
列:進程所屬用戶。FD
列:文件描述符(u
表示讀寫模式,r
表示只讀,w
表示寫入)。
2. fuser
命令
fuser
用于查找使用指定文件的進程。
命令格式:
fuser -v <文件路徑>
示例:
fuser -v /tmp/test.txt
輸出解讀:
USER
列:進程所屬用戶。PID
列:占用文件的進程 PID。TYPE
列:文件類型(如cwd
表示當前工作目錄,txt
表示可執行文件,mem
表示內存映射)。
二、文件已被刪除但進程仍持有句柄
若文件已被刪除(如通過 rm
命令刪除),但進程仍在寫入該文件(未關閉文件句柄),此時文件在磁盤上已不可見,但可通過進程的虛擬文件系統(/proc
)查看:
1. 通過 /proc/<PID>/fd
查找
每個進程的文件描述符存儲在 /proc/<PID>/fd/
目錄下,即使文件已刪除,仍可能存在指向該文件的句柄(顯示為 (deleted)
)。
步驟:
-
遍歷所有進程的
fd
目錄:for pid in /proc/[0-9]*; dols -l $pid/fd 2>/dev/null | grep "deleted" | grep "<文件名片段>" done
(將
<文件名片段>
替換為已刪除文件的部分名稱,如test.txt
) -
示例輸出:
lrwx------ 1 root root 64 Jun 5 14:30 3 -> /tmp/test.txt (deleted)
- 上述輸出表示 PID 對應目錄下的文件描述符 3 指向已刪除的
/tmp/test.txt
,該進程正在寫入該文件。
- 上述輸出表示 PID 對應目錄下的文件描述符 3 指向已刪除的
2. lsof
命令直接查詢(支持已刪除文件)
lsof
可直接列出已刪除但仍被進程打開的文件:
lsof | grep "deleted" | grep "<文件名片段>"
示例輸出:
vim 12345 user 3r REG 8,1 1234 56789 /tmp/test.txt (deleted)
PID=12345
的進程(此處為vim
)正在以只讀模式(3r
)打開已刪除的文件。
三、通過文件修改時間和進程日志輔助定位
若無法通過上述方法直接定位(如文件已關閉且未被刪除),可結合以下間接方式:
1. 查看文件修改時間
通過 stat
命令獲取文件最后修改時間:
stat /path/to/file
- 記錄時間戳后,結合系統日志(如
syslog
、auditd
)或進程監控工具(如ps aux --sort=-etime
)查找該時間段內活躍的進程。
2. 使用 auditd
審計系統
若系統啟用了 auditd
(審計服務),可配置規則監控文件修改事件:
-
添加審計規則:
auditctl -w /path/to/file -p wa -k file_modify
(監控文件的寫入(
w
)和屬性修改(a
)事件) -
觸發修改后,查看審計日志:
ausearch -k file_modify | grep pid
- 輸出中會包含修改文件的進程 PID 和用戶信息。
四、總結:常用命令速查表
場景 | 命令示例 | 說明 |
---|---|---|
文件未刪除,查找進程 | lsof /path/to/file | 直接列出打開文件的進程 |
文件未刪除,快速定位 | fuser -v /path/to/file | 顯示占用文件的進程及權限 |
文件已刪除,查找句柄 | `lsof | grep “deleted” |
遍歷進程 fd 目錄 | for pid in /proc/[0-9]*; do ls -l $pid/fd; done | 手動篩查文件描述符 |
注意事項
- 權限問題:部分文件(如系統文件)需
root
權限才能查看。 - 時效性:若進程已關閉文件或重啟,將無法通過上述方法定位。 這個問題下面的案例可以實現
- 日志依賴:
auditd
等功能需提前配置,否則無法追溯歷史修改事件。
根據文件是否被刪除及當前狀態,選擇合適的工具組合即可高效定位修改文件的進程 PID。
案例:
現在/tmp目錄被寫入了一個/tmp/20250514080520642/68244ec00b4743533ed841b6 的文件,如何編寫一個shell的監控程序,可以在下一次進程些文件的時候,可以獲取進程信息。
在傳統的 comm
命令中,-z
選項并非標準選項(可能僅在某些特定系統或版本中存在)。當出現 comm: 無效選項 -- z
錯誤時,說明當前系統的 comm
不支持帶 NUL
分隔符的 z
參數。此時需要調整腳本邏輯,改用其他方式處理包含特殊字符(如換行符)的文件路徑。以下是修改后的腳本,移除 comm -z
并改用文本行處理:
修正后的輪詢監控腳本(兼容傳統 comm)
#!/bin/bash# 純 shell 實現的 /tmp 目錄監控腳本(修復 comm -z 錯誤)
# 使用方法: ./monitor_tmp_polling.sh [輪詢間隔(秒)] [輸出日志文件]POLL_INTERVAL="${1:-1}" # 默認輪詢間隔 1 秒
LOG_FILE="${2:-/tmp/tmp_writes_monitor.log}"
TMP_DIR="/tmp"# 創建日志文件
touch "$LOG_FILE" || { echo "無法創建日志文件 $LOG_FILE" >&2; exit 1; }echo "=== 開始監控 $TMP_DIR 目錄的寫入操作 (輪詢間隔: ${POLL_INTERVAL}s) ===" | tee -a "$LOG_FILE"
echo "日志文件: $LOG_FILE"
echo "按 Ctrl+C 停止監控"
echo# 初始文件列表(每行一個文件路徑)
OLD_FILES=$(mktemp)
find "$TMP_DIR" -type f -print 2>/dev/null | sort > "$OLD_FILES"# 清理函數
cleanup() {rm -f "$OLD_FILES" "$NEW_FILES"echo "=== 監控已停止 ===" | tee -a "$LOG_FILE"exit
}trap cleanup SIGINT SIGTERMwhile true; do# 新文件列表(每行一個文件路徑)NEW_FILES=$(mktemp)find "$TMP_DIR" -type f -print 2>/dev/null | sort > "$NEW_FILES"# 找出新增的文件(通過 comm 對比,排除已存在的文件)NEW_FILES_LIST=$(comm -13 "$OLD_FILES" "$NEW_FILES") # 移除 -z 選項if [ -n "$NEW_FILES_LIST" ]; thentimestamp=$(date +"%Y-%m-%d %H:%M:%S")echo "[$timestamp] 檢測到 $(echo "$NEW_FILES_LIST" | wc -l) 個新文件" | tee -a "$LOG_FILE"# 處理每個新增文件(逐行讀取,兼容含空格的文件名)while IFS= read -r file; doecho "[$timestamp] 新文件: $file" | tee -a "$LOG_FILE"# 嘗試查找打開該文件的進程lsof_output=$(lsof "$file" 2>/dev/null)if [ -n "$lsof_output" ]; then# 提取進程信息(跳過表頭)pids=$(echo "$lsof_output" | awk 'NR>1 {print $2}')echo "[$timestamp] 發現 $(echo "$pids" | wc -w) 個進程正在訪問該文件:" | tee -a "$LOG_FILE"echo "$lsof_output" | tee -a "$LOG_FILE"echo | tee -a "$LOG_FILE"# 為每個進程獲取詳細信息for pid in $pids; doecho "[$timestamp] 進程 $pid 的詳細信息:" | tee -a "$LOG_FILE"ps -p "$pid" -o user,pid,ppid,cmd,%cpu,%mem,start,etime | tee -a "$LOG_FILE"# 獲取進程打開的所有文件描述符echo "[$timestamp] 進程 $pid 打開的文件描述符:" | tee -a "$LOG_FILE"ls -l /proc/"$pid"/fd 2>/dev/null | tee -a "$LOG_FILE"echo | tee -a "$LOG_FILE"doneelseecho "[$timestamp] 未找到訪問該文件的進程 (可能已關閉文件句柄)" | tee -a "$LOG_FILE"echo | tee -a "$LOG_FILE"fidone <<< "$NEW_FILES_LIST"fi# 更新文件列表mv "$NEW_FILES" "$OLD_FILES"# 等待下一個輪詢周期sleep "$POLL_INTERVAL"
done
使用說明
與之前的腳本相同,直接運行即可:
chmod +x monitor_tmp_polling.sh
./monitor_tmp_polling.sh # 默認輪詢間隔 1 秒,日志存于 /tmp/tmp_writes_monitor.log