Linux问题排查-找到偷偷写文件的进程
在 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