Linux下基于关键词文件搜索

背景

前面写了一篇《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
相关推荐
虚拟指尖5 小时前
Ubuntu编译安装COLMAP【实测编译成功】
linux·运维·ubuntu
椎4956 小时前
苍穹外卖前端nginx错误之一解决
运维·前端·nginx
刘某的Cloud6 小时前
parted磁盘管理
linux·运维·系统·parted
啊?啊?6 小时前
4 解锁 Linux 操作新姿势:man、grep、tar ,创建用户及添加权限等 10 大实用命令详解
linux·服务器·实用指令
程序员老舅6 小时前
干货|腾讯 Linux C/C++ 后端开发岗面试
linux·c语言·c++·编程·大厂面试题
极验6 小时前
iPhone17实体卡槽消失?eSIM 普及下的安全挑战与应对
大数据·运维·安全
爱倒腾的老唐6 小时前
24、Linux 路由管理
linux·运维·网络
程序员Aries6 小时前
自定义网络协议与序列化/反序列化
linux·网络·c++·网络协议·程序人生
yannan201903136 小时前
Docker容器
运维·docker·容器