背景
前面写了一篇《windows下基于关键词文件检索》文章,但是随着近些年,办公电脑国产化越来越普及。不得不,再编写一篇,《Linux下基于关键词文件搜索》。
思路和实现
一样使用原生的linux,shell命令行,无需任何安装。
shell
#!/bin/bash
##############################################
# search_keyword_linux.sh
# Linux 环境下的文件搜索脚本
# 功能:
# - 默认搜索两个关键词:秘密 机密(若未传关键词时使用)
# - 支持传入任意多个关键词
# - 匹配文件名、文件夹名(不区分大小写),默认不搜索文件内容
# - 跳过过大的文件(可配置)
# - 输出 TSV:Type<TAB>Size(bytes)<TAB>MTime<TAB>Path<TAB>MatchedKeywords
#
# 用法示例:
# # 使用默认关键词(摸底、报数)
# chmod +x search_keyword_linux.sh
# ./search_keyword_linux.sh
#
# # 指定关键词覆盖默认(查两个关键词)
# ./search_keyword_linux.sh -k "秘密" "机密"
#
# # 指定输出文件、仅名匹配、限制最大扫描文件 200MB
# ./search_keyword_linux.sh -o "search_results.tsv" -n -m 200
#
# # 手动指定要扫描的目录
# ./search_keyword_linux.sh -d /path/to/search
#
# 注意:
# - 建议以root权限运行以减少权限拒绝:sudo ./search_keyword_linux.sh
# - 脚本依赖 grep、find、stat、du 等基础Linux命令
##############################################
# 默认参数
KEYWORDS=()
OUTPUT_FILE="search_results.tsv"
NO_CONTENT_SEARCH=true
MAX_FILE_SIZE_MB=500
# 忽略的目录(用空格分隔)
IGNORE_DIRS=("git" "node_modules" "__pycache__")
# 用户手动指定的目录
USER_DIRS=()
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case $1 in
-k|--keywords)
shift
while [[ $# -gt 0 && ! $1 == -* ]]; do
KEYWORDS+=($1)
shift
done
;;
-o|--output)
OUTPUT_FILE=$2
shift 2
;;
-n|--no-content-search)
NO_CONTENT_SEARCH=true
shift
;;
-m|--max-size)
MAX_FILE_SIZE_MB=$2
shift 2
;;
-d|--dir)
shift
while [[ $# -gt 0 && ! $1 == -* ]]; do
USER_DIRS+=($1)
shift
done
;;
*)
echo "未知参数: $1"
echo "使用方法: $0 [-k keyword1 keyword2 ...] [-o output_file] [-n] [-m max_file_size_mb] [-d directory1 directory2 ...]"
exit 1
;;
esac
done
# 如果未提供关键词,使用默认关键词
if [ ${#KEYWORDS[@]} -eq 0 ]; then
KEYWORDS=("秘密" "机密")
fi
# 转换最大文件大小到字节
MAX_FILE_SIZE_BYTES=$((MAX_FILE_SIZE_MB * 1024 * 1024))
# 初始化输出文件
if ! echo -e "Type\tSize(bytes)\tMTime\tPath\tMatchedKeywords" > "$OUTPUT_FILE" 2>/dev/null; then
echo "警告:无法创建输出文件 $OUTPUT_FILE,尝试在当前目录创建"
OUTPUT_FILE="./search_results.tsv"
if ! echo -e "Type\tSize(bytes)\tMTime\tPath\tMatchedKeywords" > "$OUTPUT_FILE" 2>/dev/null; then
echo "错误:无法创建输出文件,请检查权限"
exit 1
fi
fi
# 输出文件路径
if command -v realpath > /dev/null; then
echo "输出文件: $(realpath "$OUTPUT_FILE" 2>/dev/null || echo "$OUTPUT_FILE")"
else
echo "输出文件: $OUTPUT_FILE"
fi
# 获取所有挂载的磁盘分区或使用用户指定的目录
DRIVES=()
# 如果用户手动指定了目录,优先使用
if [ ${#USER_DIRS[@]} -gt 0 ]; then
echo "使用用户指定的目录进行扫描"
for dir in "${USER_DIRS[@]}"; do
if [ -d "$dir" ]; then
DRIVES+=($dir)
else
echo "警告:目录 $dir 不存在或无法访问"
fi
done
else
# 收集所有挂载点,改进的检测逻辑
if df -h > /dev/null 2>&1; then
echo "正在获取系统挂载点..."
# 使用更通用的方法获取挂载点,不局限于/dev/开头的设备
# 跳过tmpfs、devtmpfs、overlay等临时或特殊文件系统
df -h | tail -n +2 | while read -r line; do
# 提取挂载点(最后一列)
drive=$(echo "$line" | awk '{print $NF}')
# 提取文件系统类型(第二列,某些系统可能需要调整)
fs_type=$(echo "$line" | awk '{print $2}')
# 跳过特殊文件系统和不需要扫描的目录
if [ -d "$drive" ] && [[ $drive != "/boot"* ]] && [[ $drive != "/var"* ]] && [[ $drive != "/tmp"* ]] && \
[[ $fs_type != "tmpfs" ]] && [[ $fs_type != "devtmpfs" ]] && [[ $fs_type != "overlay" ]] && [[ $fs_type != "squashfs" ]]; then
DRIVES+=($drive)
fi
done
# 显示找到的分区,便于调试
if [ ${#DRIVES[@]} -gt 0 ]; then
echo "找到的挂载点: ${DRIVES[@]}"
else
echo "警告:未找到符合条件的挂载点,尝试使用根目录和常用挂载点"
# 添加常见的挂载点作为后备
common_mounts=("/" "/home" "/mnt" "/media")
for mnt in "${common_mounts[@]}"; do
if [ -d "$mnt" ]; then
DRIVES+=($mnt)
fi
done
fi
else
echo "警告:无法获取磁盘分区信息,使用当前目录和常见挂载点"
# 添加当前目录和常见挂载点
DRIVES=("$(pwd)" "/home" "/mnt" "/media")
fi
fi
# 如果没有找到合适的目录,默认至少扫描当前目录
if [ ${#DRIVES[@]} -eq 0 ]; then
echo "警告:没有可用的扫描目录,使用当前目录"
DRIVES=($(pwd))
fi
# 去重,避免重复扫描
DRIVES=($(echo "${DRIVES[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
# 辅助函数:检查一个字符串包含哪些关键词(返回逗号分隔)
get_matched_keywords() {
local text="$1"
local matched=()
for kw in "${KEYWORDS[@]}"; do
if [[ "$text" == *"$kw"* ]]; then
matched+=($kw)
fi
done
# 用逗号连接匹配的关键词
local IFS=,
echo "${matched[*]}"
}
# 搜索每个磁盘分区
for root in "${DRIVES[@]}"; do
echo "扫描分区:$root"
# 构建忽略目录的find参数 - 修复:去掉local关键字
ignore_args=()
for dir in "${IGNORE_DIRS[@]}"; do
ignore_args+=(-path "*$dir*" -prune -o)
done
# 搜索所有文件和目录 - 增强错误处理
if ! find "$root" -xdev "${ignore_args[@]}" -print 2>/dev/null | while read -r path; do
# 跳过无法访问的文件
if [ ! -e "$path" ]; then
continue
fi
# 处理目录
if [ -d "$path" ]; then
# 获取目录名
dir_name="$(basename "$path" 2>/dev/null)"
if [ -z "$dir_name" ]; then
continue
fi
# 检查目录名是否包含关键词
matched=$(get_matched_keywords "$dir_name")
if [ -n "$matched" ]; then
# 获取修改时间(使用stat命令,不同Linux版本输出可能不同)
mtime=""
if command -v stat > /dev/null; then
# GNU stat (Linux)
mtime=$(stat -c "%y" "$path" 2>/dev/null | cut -d' ' -f1,2 | cut -d'.' -f1)
fi
if [ -z "$mtime" ]; then
mtime="未知"
fi
# 输出结果
output_line="文件夹\t\t$mtime\t$path\t$matched"
echo -e "$output_line"
echo -e "$output_line" >> "$OUTPUT_FILE" 2>/dev/null
fi
# 处理文件
elif [ -f "$path" ]; then
# 获取文件名
file_name="$(basename "$path" 2>/dev/null)"
if [ -z "$file_name" ]; then
continue
fi
# 检查文件名是否包含关键词
matched_name=$(get_matched_keywords "$file_name")
if [ -n "$matched_name" ]; then
# 获取文件大小和修改时间
file_size="未知"
file_size=$(du -b "$path" 2>/dev/null | cut -f1)
mtime=""
if command -v stat > /dev/null; then
mtime=$(stat -c "%y" "$path" 2>/dev/null | cut -d' ' -f1,2 | cut -d'.' -f1)
fi
if [ -z "$mtime" ]; then
mtime="未知"
fi
# 输出结果
output_line="文件名匹配\t$file_size\t$mtime\t$path\t$matched_name"
echo -e "$output_line"
echo -e "$output_line" >> "$OUTPUT_FILE" 2>/dev/null
fi
# 文件内容匹配(可选)
if [ "$NO_CONTENT_SEARCH" = false ]; then
# 检查文件大小是否超过限制
file_size="0"
file_size=$(du -b "$path" 2>/dev/null | cut -f1)
if [ -n "$file_size" ] && [ "$file_size" -le "$MAX_FILE_SIZE_BYTES" ]; then
# 检查文件内容是否包含任何关键词
matched_content=()
for kw in "${KEYWORDS[@]}"; do
# 使用grep搜索文件内容,忽略二进制文件
if grep -q -a "$kw" "$path" 2>/dev/null; then
matched_content+=($kw)
fi
done
if [ ${#matched_content[@]} -gt 0 ]; then
# 格式化匹配的关键词
local IFS=,
matched_content_str="${matched_content[*]}"
# 获取修改时间
mtime=""
if command -v stat > /dev/null; then
mtime=$(stat -c "%y" "$path" 2>/dev/null | cut -d' ' -f1,2 | cut -d'.' -f1)
fi
if [ -z "$mtime" ]; then
mtime="未知"
fi
# 输出结果
output_line="文件内容匹配\t$file_size\t$mtime\t$path\t$matched_content_str"
echo -e "$output_line"
echo -e "$output_line" >> "$OUTPUT_FILE" 2>/dev/null
fi
fi
fi
fi
done; then
echo "警告:扫描分区 $root 时遇到权限限制,部分文件可能无法访问"
fi
done
echo "搜索完成。结果写入:$(realpath "$OUTPUT_FILE" 2>/dev/null || echo "$OUTPUT_FILE")"
read -p "按Enter键退出..."
使用方法
脚本授权
shell
chmod +x search_keyword_linux.sh
运行脚本
shell
./search_keyword_linux.sh