告别AI知识库污染:clean4llm.sh代码清洗工具
背景
我的工作平台是Eclipse+VSCode,VsCode中用阿里的通义灵码
和腾讯AI助手
也是轮换着用。
IDE中的AI助手用着是方便,但是向AI提问的上下文最多仅限于当前工作空间(Workspace),对于需要跨项目的复杂问题,因为上下文的限制,就没办法仅在IDE中用AI助手解决。
所以我还得依赖Cherry Studio建立知识库,来将解决复杂问题需要的不同数据整合到知识,再与AI交互。
比如为了设计一个maven插件来修改class字节码,我需要将自己的插件项目源码、maven插件相关文档/项目源码,以及ASM相关文档和源码都要加入知识库。这样AI才能更好的理解需求,生成更准确的解决方案。
所以最近的工作在是学习在DeepSeek的帮助下解决一些复杂问题。对Cherry Studio建立知识库的也是在这个学习的过程中熟悉的。
我们知道,Cherry Studio是可以将一个文件夹加入知识库,也就是可以将一个项目文件夹整个拖到知识库。
与anythingLLM相比,这很方便,但到目前为止最新的v1.0.6
版本还没有提供文件过滤功能。也就是说它会不加区分地将文件夹中的所有文件都向量化添加到知识库。
所以在这个过程中,可能你也会遇到与我一样的问题:
尝试将Git代码仓库导入LLM知识库时,常常会遇到一个棘手的问题:仓库中混杂着大量二进制文件(如图片、视频、项目编译中间产物等),这些文件不仅对语言模型毫无价值,还会影响知识库的质量和检索效率。传统的手动过滤方式效率低下,而简单的复制粘贴更会导致"知识库污染"。
即使不考虑污染问题,过多的对LLM无用数据,也会导致向量化时间过乃至失败,比如我的尝试将一个提交纪录很多的Git仓库就总是失败。
这正是clean4llm.sh
诞生的背景——我决定写一个简单的bash shell脚本来解决这个问题,它就像一位专业的代码清洁工,能够自动识别并过滤各类无效文件,确保你添加到知识库的数据只保留纯净的源代码。
DeepSeek出品
要是搁以前,我得手撸这个脚本,好麻烦。
这次我只是初步写了我的需求,让DeepSeek来帮我完成脚本的初稿:
# clean4llm.sh实现要求
创建一个 bash shell 脚本 clean4llm.sh,确保也可以在windows平台下的git bash中运行,实现从指定本地文件夹(多数情况下是git代码仓库)递归复制所有文件到新文件夹,并排除指定文件,**目标是只保留文本格式的原始文件,排除所有二进制文件和中间文件**。
1. 目标文件夹位置可以通过命令行参数--output指定,如果未指定则默认为`${原文件夹名}_src`,保存在当前文件夹下
2. 如果目标文件存在则删除重新创建
3. 任何一步操作出错误就退出脚本执行
4. 如果存在.gitignore文件,则根据文件夹中指定的忽略列表排除指定文件,使用git check-ignore来排除忽略文件
5. 强制忽略,不论是否存在存在.gitignore文件,都要忽略以下文件:
- 所有常用二进制文件的后缀的文件,如`.exe`,`.bin`,`.o`,`.obj`。
- 所有图像格式文件
- 忽略医学专用图像文件
- 忽略视频格式文件
- 忽略音频格式文件
- 忽略数据库文件
- 忽略二进制模型文件
- 忽略所有各种编译器生成的中间文件和目标文件,比如`.jar`,'.class',
- 忽略软件项目常用的目标代码生成文件夹,比如`bin`,`build`,`target`,`lib`
- 忽略二进制格式保存的office文件:`.doc`,`.xls`,`.ppt`
- 忽略所有压缩文件,比如`.zip`,`.rar`,`.7z`
- 忽略所有以'.'为前缀的文件夹和文件,比如`.git`
- 忽略所有以'~'为前缀的文件夹和文件
- 忽略所有程序运行生成的文件或文件夹,比如`.log`,或文件夹`log`
clean4llm.sh
很快DeepSeek就给出了代码,初步测试可用,后续我又与它做了多次交互,改进了代码。就有了现在成品的样子。
你可以根据自己的实际需要在FILE_EXCLUDE_RULES
,PATH_EXCLUDE_RULES
增减自己的过滤条件。如果你对这个脚本的功能有啥不满意,你可以让DeepSeek继续改,改到你满意为止喽。
#!/usr/bin/env bash
#
# clean4llm(Clean for LLM),项目代码添加到知识库的辅助工具
# 实现将指定本地文件夹(多数情况下是git代码仓库)递归复制所有源码文件到新文件夹,并排除指定文件二进制文件
# 可以通过修改 FILE_EXCLUDE_RULES 和 FILE_EXCLUDE_RULES_EXCLUDE 两个变量自定义排除规则
# 输出错误信息并退出
set -eo pipefail
# 错误退出时输出错误信息
trap 'echo "错误:在行 ${LINENO} 执行命令失败 - 命令: $BASH_COMMAND, 退出码: $?" >&2' ERR
# 输出错误信息中记录完整调用栈
# trap 'echo "错误追踪:
# 文件: ${BASH_SOURCE[0]}
# 行号: ${LINENO}
# 调用栈: ${FUNCNAME[*]}
# 失败命令: $BASH_COMMAND
# 退出码: $?" >&2' ERR
# 显示帮助信息
function show_help() {
cat <<EOF
clean4llm - 代码仓库清理工具 v1.0
用途:清理并复制代码仓库到指定目录,排除二进制文件
使用方法:
$0 [选项] [源目录]
选项:
-v 显示详细输出
--output=目录 指定输出目录(默认:./源目录名_src)
-h
--help 显示帮助信息
示例:
# 清理当前git仓库到默认输出目录
$0
# 指定源目录和输出目录
$0 /path/to/src --output=~/cleaned_code
文件筛选:
• 自动排除二进制文件(如exe, jar, png等)
• 自动排除版本控制目录(.git等)
• 支持.gitignore规则
EOF
}
# 默认不输出详细日志
VERBOSE=
# 源目录
SOURCE_DIR=""
# 目标目录
TARGET_DIR=""
# 参数解析
while [[ $# -gt 0 ]]; do
case "$1" in
-v)
VERBOSE=1
shift
;;
--output)
TARGET_DIR="$2"
shift 2
;;
--output=*)
TARGET_DIR="${1#*=}"
shift
;;
-h)
show_help
exit 0
;;
--help)
show_help
exit 0
;;
*)
# 增加对无效参数的检测
if [[ "$1" == -* ]]; then
echo "错误:无效参数 '$1'" >&2
show_help
exit 1
fi
SOURCE_DIR="$1"
shift
;;
esac
done
if [[ -z "$SOURCE_DIR" ]]; then
# 检测当前目录是否有.git文件夹
if [[ -d ".git" ]]; then
SOURCE_DIR=$(pwd)
[[ -n "$VERBOSE" ]] && echo "检测到.git目录,自动设置源目录为当前目录: $SOURCE_DIR"
fi
fi
# 将SOURCE_DIR 转为全路径
if [[ -n "$SOURCE_DIR" ]]; then
SOURCE_DIR=$(cd -- "$SOURCE_DIR" && pwd)
fi
# 校验源目录
if [[ ! -d "$SOURCE_DIR" ]]; then
echo "错误:请指定有效源目录,或确保当前目录包含.git文件夹" >&2
exit 1
fi
# 设置目标目录
if [[ -z "$TARGET_DIR" ]]; then
SOURCE_BASENAME=$(basename "$SOURCE_DIR")
TARGET_DIR="$(pwd)/${SOURCE_BASENAME}_src"
fi
# 清理并创建目标目录
rm -rf "$TARGET_DIR"
mkdir -p "$TARGET_DIR"
TARGET_DIR=$(cd -- "$TARGET_DIR" && pwd)
# 文件排除规则
declare -a FILE_EXCLUDE_RULES=(
# 强制忽略的二进制文件类型
'*.exe' '*.bin' '*.so' '*.pyd'
'*.doc' '*.xls' '*.ppt'
'*.a' '*.lib' '*.o' '*.obj'
'*.class' '*.prefs'
# 强制忽略 java 打包文件
'*.jar' '*.war' '*.ear'
# 强制忽略压缩包
'*.zip' '*.rar' '*.7z' '.bz2' '*.iso' '*.tar' '*.gz' '*.tgz'
'*.xz' '*.z' '*.arj' '*.cab' '*.lzh' '*.ex_' '*.dl_'
'*.log'
# 强制忽略图像格式文件
'*.png' '*.jpg' '*.jpeg' '*.gif' '*.bmp' '*.tiff' '*.tif' '*.webp' '*.psd' '*.raw'
'*.heic' '*.pcx' '*.tga' '*.jp2' '*.j2k' '*.iff' '*.fpx'
# 强制忽略矢量图像格式
'*.svg' '*.ai' '*.eps'
'*.cdr' '*.dwg' '*.dxf' '*.ico' '*.xbm' '*.xpm'
# 强制忽略医学专用图像文件
'*.dcm' '*.nii' '*.img' '*.hdr' '*.nrrd' '*.mnc' '*.par' '*.rec' '*.svs'
# 强制忽略视频格式文件
'*.mp4' '*.avi' '*.mkv' '*.mov' '*.flv' '*.wmv' '*.rmvb' '*.3gp'
'*.webm' '*.ts' '*.m2ts' '*.qlv' '*.vob' '*.asf'
# 强制忽略音频格式文件
'*.wav' '*.mp3' '*.flac' '*.aac' '*aif' '*.aiff' '*.ape' '*.ogg' '*.wma'
'*.mid' '*.midi' '*.cda' '*.ra' '*.rm' '*.au' '*.vqf' '*.dsf' '*.dff'
# 强制忽略数据库文件
'*.rdb' '*.aof' '*.db' '*.mdf' '*.ndf' '*.ldf' '*.frm' '*.ibd' '*.myd' '*.myi' '*.dbf'
'*.ctl' '*.dmp' '*.dat' '*.backup' '*.sqlite' '*.mdb' '*.accdb' '*.bson' '*.ibdata'
'*.fdb' '*.hdb' '*.backup'
# 强制忽略二进制模型文件
'*.gguf' '*.pt' '*.pth' '*.ckpt' '*.onnx' '*.safetensors'
'.project' '.classpath'
# 特殊文件模式
'.*' '~*'
)
# 文件夹排除规则
PATH_EXCLUDE_RULES=(
# 强制忽略的目录
'bin/' 'build/' 'target/' 'lib/' 'log/' 'release/' '.*/'
)
# 构建排除表达式
EXCLUDE_PATTERN=()
# 1. 路径排除(目录级别)
for _dir in "${PATH_EXCLUDE_RULES[@]}"; do
_dir="${_dir#/}" # 删除开头的/
_dir="${_dir%/}" # 删除结尾的/
EXCLUDE_PATTERN+=( -path "*/$_dir/*" -prune -o )
done
# 2. 文件排除(文件级别)
if [ "${#FILE_EXCLUDE_RULES[@]}" -gt 0 ]; then
EXCLUDE_PATTERN+=( \( -type f \( )
for _ext in "${FILE_EXCLUDE_RULES[@]}"; do
EXCLUDE_PATTERN+=( -name "$_ext" -prune -o )
done
EXCLUDE_PATTERN=("${EXCLUDE_PATTERN[@]::${#EXCLUDE_PATTERN[@]}-1}")
EXCLUDE_PATTERN+=( \) \) -o )
fi
# 预计算是否启用git check-ignore
use_git_ignore=0
if [ -f "$SOURCE_DIR/.gitignore" ] && command -v git &>/dev/null; then
use_git_ignore=1
fi
# 生成临时文件列表(同时处理.gitignore过滤)
echo -e "\033[34m统计文件数量。。。\033[0m"
tmp_list=$(mktemp)
# 待复制文件总数
total=0
{
while IFS= read -r -d '' file; do
[ "$use_git_ignore" -eq 1 ] &&
git -C "$SOURCE_DIR" check-ignore -q "${file#$SOURCE_DIR/}" && continue
echo "$file"
total=$((total + 1))
done < <(find "$SOURCE_DIR" "${EXCLUDE_PATTERN[@]}" -type f -print0)
} > "$tmp_list"
if [[ $total -eq 0 ]]; then
echo -e "\033[33m没有发现需要复制的文件\033[0m"
rm -f "$tmp_list"
exit 0
fi
echo "开始复制 $SOURCE_DIR 的 $total 个文件"
echo # 增加空行用于光标操作
# 统计文件类型
declare -A file_stats=()
# 复制文件计数器
count=0
while IFS= read -r file; do
rel_path="${file#$SOURCE_DIR/}"
dest="$TARGET_DIR/$rel_path"
mkdir -p "$(dirname "$dest")"
cp "$file" "$dest"
filename=$(basename "$rel_path")
# 后缀判定逻辑
if [[ "$filename" == *.* ]]; then
# 处理正常前缀+后缀或纯后缀类型
if [[ "$filename" =~ ^\..+$ && $(tr -dc '.' <<< "$filename" | wc -c) -eq 1 ]]; then
# 纯后缀类型(如 .bashrc)
key="$filename"
else
# 正常前后缀组合(如 file.txt)
suffix="${filename##*.}"
key="*.$suffix"
fi
else
# 无后缀类型(如 LICENSE)
key="$filename"
fi
if [[ ! "${file_stats[$key]}" ]]; then
file_stats["$key"]=0
fi
file_stats["$key"]=$(( file_stats["$key"] + 1)) # 统一计数加1
count=$((count + 1))
percent=$((count * 100 / total))
cols=$(tput cols)
bar_len=$((cols - 20))
filled=$((bar_len * count / total))
# 处理文件名折叠
max_path_len=$((cols - 12)) # 前缀"当前文件: "占12字符宽度
if [[ ${#rel_path} -gt $max_path_len ]]; then
folded_length=$((max_path_len - 3))
front_length=$((folded_length / 2))
end_length=$((folded_length - front_length))
front_part="${rel_path:0:front_length}"
end_part="${rel_path: -end_length}"
folded_path="${front_part}...${end_part}"
else
folded_path="$rel_path"
fi
# 光标控制:覆盖前一次输出
if [[ $count -gt 1 ]]; then
printf "\033[2A" # 上移两行
fi
# 清除行并输出进度条
printf "\033[K|%-*s| %3d%% (%d/%d)\n" \
"$bar_len" \
"$(printf '#%.0s' $(seq 1 "$filled"))" \
"$percent" \
"$count" \
"$total"
# 清除行并输出文件名
printf "\033[K当前文件: %s\n" "$folded_path"
done < "$tmp_list"
echo # 换行保证输出整洁
rm -f "$tmp_list"
echo -e "\033[32m复制完成:源目录已复制到\033[0m $TARGET_DIR"
# 输出统计信息
echo -e "===== 统计信息 ====="
if [[ ${#file_stats[@]} -gt 0 ]]; then
{
echo "类型 数量" # 手动添加表头
for key in "${!file_stats[@]}"; do
echo "$key ${file_stats[$key]}"
done
} | column -t
else
echo "无"
fi
快速上手
1. 基础使用
./clean4llm.sh your_project/
自动在当前位置生成your_project_src
目录,内含清洗后的纯净代码
2. 自定义输出目录
./clean4llm.sh your_project/ --output=~/clean_code/
技术逻辑及特性
基础架构保障
- 跨平台支持:通过纯Bash实现,兼容Linux/macOS/Git Bash
- 安全机制:
set -eo pipefail
确保任何步骤出错立即终止 - 路径处理:自动处理相对/绝对路径,适配各种目录结构
智能过滤系统
动态排除规则生成示例:
# 生成find命令的排除参数
EXCLUDE_PATTERN=(
-path "*/bin/*" -prune -o
-path "*/build/*" -prune -o
\(
-type f \(
-name "*.exe" -o
-name "*.jpg" -o
... \)
\) -o
)
Git深度整合
- 自动识别
.gitignore
规则 - 使用
git check-ignore
进行精确过滤 - 保留Git原生忽略逻辑的完整性
增强显示
实时进度条
复制过程实时显示进度条(支持终端列宽自适应,智能路径折叠显示)
|#################......| 65% (132/203)
当前文件: src/.../utils.py
统计报表
复制完成输出文件类型统计报表
===== 统计信息 =====
类型 数量
*.py 142
*.md 23
Dockerfile 5
LICENSE 1
Python版本优势
新增的Python版本实现相比原Bash版本具有显著性能提升:
- Windows系统:快10倍以上
- Linux系统:快约1倍
- 跨平台支持更好
Python版本使用
# 基础使用
python clean4llm.py /path/to/project
# 自定义输出目录
python clean4llm.py ~/my_project --output ~/clean_code
# 显示过滤文件列表
python clean4llm.py ~/project -v
依赖说明
需额外安装以下Python库:
pip install tqdm wcwidth
tqdm
:用于显示进度条wcwidth
:支持终端正确显示特殊字符
代码仓库
完整代码保存在码云仓库:10km/clean4llm