惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Archives - TechRepublic
Security Archives - TechRepublic
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
Scott Helme
Scott Helme
NISL@THU
NISL@THU
Cisco Talos Blog
Cisco Talos Blog
C
Cybersecurity and Infrastructure Security Agency CISA
AWS News Blog
AWS News Blog
V
Vulnerabilities – Threatpost
J
Java Code Geeks
U
Unit 42
The GitHub Blog
The GitHub Blog
H
Help Net Security
T
Tenable Blog
aimingoo的专栏
aimingoo的专栏
Jina AI
Jina AI
Spread Privacy
Spread Privacy
Apple Machine Learning Research
Apple Machine Learning Research
人人都是产品经理
人人都是产品经理
L
Lohrmann on Cybersecurity
T
Threatpost
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Engineering at Meta
Engineering at Meta
A
About on SuperTechFans
I
InfoQ
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog
L
LINUX DO - 最新话题
K
Kaspersky official blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Threat Research - Cisco Blogs
C
Check Point Blog
T
The Blog of Author Tim Ferriss
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享
Help Net Security
Help Net Security
Google DeepMind News
Google DeepMind News
A
Arctic Wolf
Y
Y Combinator Blog
N
News | PayPal Newsroom
M
MIT News - Artificial intelligence
Latest news
Latest news
H
Hacker News: Front Page
Blog — PlanetScale
Blog — PlanetScale
腾讯CDC
I
Intezer
爱范儿
爱范儿
F
Fortinet All Blogs
P
Palo Alto Networks Blog
C
CERT Recently Published Vulnerability Notes

lvbibir's Blog

Linux cat 和 tee 命令写入文件 python | 使用 uv 管理你的 python 环境 Claude Code 完整配置指南 windows | mihomo 内核独立部署指南 linux | 磁盘扩容 wsl | 释放长久运行占用的磁盘空间 mysql | 线程上限问题处理 麒麟 7.6 安装谷歌 OTP 认证模块 suse 12sp5 升级 openssh 及 openssl suse 12sp5 部署 mysql 5.7 《谁的青春不迷茫》 docker | centos7 部署 docker vim | 基础配置和使用 windows | rime 输入法 & 雾凇方案 shell | sshpass 批量传输文件及执行命令 wsl | 原生 linux 方式安装 docker nodejs | fnm + pnpm 开发环境配置 wsl | 安装配置 miniconda 虚拟环境 wsl | 自动更新系统代理 wsl | bashrc 环境变量不正确加载的处理方法 wsl | win10 安装 wsl2 troubleshooting | ssh 成功但是 scp 失败 linux | 常用命令总结 docker 部署 piclist shell | 检测网站存活并自动钉钉告警 Zabbix | 监控端口连通性并自动追踪 TCP 路由 windows | 自定义开机快速启动项 windows | miniconda 配置 python 虚拟环境 Zabbix | 监控主机到指定 ip 的流量大小 shell | centos 初始化 loki (二) 部署 loki (一) 简介 prometheus (六) Alertmanager prometheus (五) 记录规则与告警规则 prometheus (四) 黑盒监控 prometheus (三) 服务发现 prometheus (二) 静态配置 prometheus (一) 简介及部署 traefik (四) TraefikService 服务 traefik (三) Middleware 中间件 traefik (二) ingressRoute 路由 traefik (一) 简介、部署和配置 kubernetes | Gateway API 简介及部署 linux | dns 配置文件中 search 和 options ndots 详解 kubernetes | command args 和 dockerfile 中的 ENTRYPOINT CMD kubernetes | statefulset 控制器详解 docker | dockerfile 最佳实践 docker | dockerfile 指令详解 linux | kill 命令详解以及 linux 中的信号 troubleshooting | elasticsearch 安装插件报错 shell | if 条件判断 docker | 下载外网镜像的几种方式 python | 鬼谷子数学问题 steam挂刀教程 linux | history 命令的格式化输出 shell | 将本地镜像批量推送到 harbor windows | hosts 文件修复 kubernetes | configmap & secret kubernetes | RBAC 鉴权和 NetworkPolicy kubernetes | service & ingress kubernetes | 存储 kubernetes | 滚动升级和自动伸缩 kubernetes | 控制器 kubernetes | 日志 kubernetes | 调度 kubernetes | pod kubernetes | 杂记 linux | set 命令详解 ceph | openeuler 部署 ceph-v16 ceph | ceph-v16 离线安装解决方案 pxe 如何应对复杂的服务器硬件环境 python | 批量修改目录下文件名 《微习惯》 ceph | openeuler (aarch64) 部署 ceph-v16 troubleshooting | glibc 误升级后修复 pxe 安装配置大全 《人间失格》 跑步日常 [置顶] Hello, hugo! shell | 检索某 url 中所有文件的内容 shell | 不同执行方式的区别 shell | 开启 debug 模式 vscode | 常见问题 windows & linux 多网卡时设置默认路由以及添加静态路由 mysql (二) 主从复制原理 GTID 并行复制 mysql (一) 部署 mysql | 杂记 cicd | jenkins 部署 mall-swarm 项目 httpd 源码打包编译成 rpm 包 openssl 源码打包编译成 rpm 包 centos7 | 修改网卡名称 ceph | pool pg_num 配置 docker | 脚本方式批量导出/导入镜像 centos 密码尝试次数过多问题处理 centos7 | 升级内核至 5.10 CVE-1999-0526 ceph | centos7 部署 ceph-v12 python | 修改 pip 源 troubleshooting | 安装 cloud-init 后导致 ssh 连接失败 windows | autohotkey 常用脚本
shell | 磁盘空间分析脚本
lvbibir · 2026-01-27 · via lvbibir's Blog

0 前言

一个高性能的 Bash 磁盘分析脚本,用于快速定位磁盘空间占用问题.

1 功能特性

  1. 子目录占用排行

    • 显示占用超过 10% 的子目录
    • 对占用超过 50% 的目录自动递归分析
    • 树形结构展示,一目了然
  2. 大文件查找

    • 查找超过指定大小的文件(默认 1024MB)
    • 显示 Top 5 大文件
    • 显示文件大小,修改日期,所有者
  3. 性能优化

    • 使用关联数组缓存目录大小
    • 一次扫描,多次查询
    • 无临时文件,减少 I/O 开销

2 使用方法

2.1 脚本内容

#!/bin/bash
# ==============================================================================
# 磁盘分析脚本 (性能优化版)
# 功能: 1. 子目录占用排行(>10%,自动递归>50%)  2. 大文件 Top 5
# 用法: ./disk_analyze.sh [目录] [大文件阈值MB]
# 优化: 使用关联数组索引缓存,减少 du 调用,消除临时文件
# ==============================================================================

set -o pipefail

# --- 参数 ---
TARGET_DIR="${1:-$(pwd)}"
LARGE_FILE_LIMIT_MB="${2:-1024}"
TOP_N=5
SHOW_THRESHOLD=10
RECURSE_THRESHOLD=50

if ! [[ "$LARGE_FILE_LIMIT_MB" =~ ^[0-9]+$ ]]; then
    echo "错误: 大文件阈值MB 必须是整数, 当前: $LARGE_FILE_LIMIT_MB"
    exit 1
fi

# 转为绝对路径
if [ -d "$TARGET_DIR" ]; then
    TARGET_DIR=$(cd "$TARGET_DIR" && pwd)
else
    echo "错误: 目录 $TARGET_DIR 不存在!"
    exit 1
fi

# --- 颜色 ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'

# --- 目录索引(核心优化:一次扫描,多次查询)---
declare -A DIR_SIZE=()
declare -A DIR_CHILDREN=()

# 进度显示函数
show_progress() {
    local count="$1"
    local current_path="$2"
    local spinner=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
    local idx=$((count / 10 % 10))
    # 截断过长的路径
    local display_path="$current_path"
    if [ ${#display_path} -gt 50 ]; then
        display_path="...${display_path: -47}"
    fi
    printf "\r    ${CYAN}${spinner[$idx]}${NC} 扫描中: ${YELLOW}%d${NC} 个目录  %-50s" "$count" "$display_path" >&2
}

build_dir_index() {
    local root="$1"

    DIR_SIZE=()
    DIR_CHILDREN=()

    local dir_count=0
    local last_update=0

    # du 默认只输出目录. 使用 -0 避免路径含空格/换行时被 read 拆分.
    while IFS=$'\t' read -r -d '' size path; do
        [ -z "$size" ] && continue
        DIR_SIZE["$path"]="$size"

        dir_count=$((dir_count + 1))

        # 每 100 个目录或每秒更新一次进度
        if (( dir_count - last_update >= 100 )); then
            show_progress "$dir_count" "$path"
            last_update=$dir_count
        fi

        if [ "$path" != "$root" ]; then
            local parent="${path%/*}"
            [ -z "$parent" ] && parent="/"
            DIR_CHILDREN["$parent"]+="$path"$'\n'
        fi
    done < <(du -b -0 -x "$root" 2>/dev/null)

    # 清除进度行,显示完成信息
    if (( dir_count > 0 )); then
        printf "\r    ${GREEN}${NC} 扫描完成: ${YELLOW}%d${NC} 个目录%-50s\n" "$dir_count" "" >&2
    fi
}

# --- 工具函数(优化:使用 Bash 原生算术替代 awk)---
human_size() {
    local bytes="${1:-0}"
    [ -z "$bytes" ] || [ "$bytes" = "0" ] && echo "0B" && return

    local div unit
    if (( bytes >= 1073741824 )); then
        div=1073741824
        unit="G"
    elif (( bytes >= 1048576 )); then
        div=1048576
        unit="M"
    elif (( bytes >= 1024 )); then
        div=1024
        unit="K"
    else
        echo "${bytes}B"
        return
    fi

    local tenths=$(( (bytes * 10 + div / 2) / div ))
    printf "%d.%d%s" $((tenths / 10)) $((tenths % 10)) "$unit"
}

# 显示目录下 Top N 文件/子目录(优化:使用 awk 插入排序替代 sort | head)
show_top_items() {
    local dir="$1"
    local prefix="$2"
    local dir_size="$3"

    printf "${prefix}${BLUE}┌─ 热点目录: %s${NC}\n" "$dir"
    printf "${prefix}${BLUE}${NC}\n"

    while IFS='|' read -r size type name; do
        [ -z "$size" ] && continue

        local pct=0
        if (( dir_size > 0 )); then
            pct=$(( (size * 100 + dir_size / 2) / dir_size ))
        fi

        if [ "$type" = "D" ]; then
            printf "${prefix}${BLUE}${NC}  %-30s ${CYAN}%8s  %3s%%${NC}  [目录]\n" "$name" "$(human_size "$size")" "$pct"
        else
            printf "${prefix}${BLUE}${NC}  %-30s ${YELLOW}%8s  %3s%%${NC}  [文件]\n" "$name" "$(human_size "$size")" "$pct"
        fi
    done < <(
        {
            children="${DIR_CHILDREN["$dir"]}"
            if [ -n "$children" ]; then
                while IFS= read -r child; do
                    [ -z "$child" ] && continue
                    size="${DIR_SIZE["$child"]}"
                    [ -z "$size" ] && continue
                    printf '%s|D|%s\n' "$size" "${child##*/}"
                done <<< "$children"
            fi

            find -L "$dir" -mindepth 1 -maxdepth 1 -xdev -type f -printf '%s|F|%f\n' 2>/dev/null
        } | awk -v n="$TOP_N" -F '|' '
            BEGIN { count=0 }
            {
                size=$1+0
                line=$0
                pos=count+1
                for (i=1; i<=count; i++) {
                    if (size > sizes[i]) { pos=i; break }
                }
                if (pos > n) next
                if (count < n) count++
                for (j=count; j>pos; j--) {
                    sizes[j]=sizes[j-1]
                    lines[j]=lines[j-1]
                }
                sizes[pos]=size
                lines[pos]=line
            }
            END { for (i=1; i<=count; i++) print lines[i] }
        '
    )

    printf "${prefix}${BLUE}└────────────────────────────────────────────${NC}\n"
}

# ==============================================================================
# [1] 子目录占用分析 (递归) - 优化:使用缓存索引
# ==============================================================================
analyze_directory() {
    local dir="$1"
    local prefix="$2"
    local parent_size="$3"

    # 获取当前目录总大小(从缓存)
    local total_size="${parent_size:-${DIR_SIZE["$dir"]}}"

    [ -z "$total_size" ] || [ "$total_size" -eq 0 ] && return

    # 收集符合条件的子目录(从缓存)
    local children="${DIR_CHILDREN["$dir"]}"
    [ -z "$children" ] && return

    local items=()
    while IFS= read -r path; do
        [ -z "$path" ] && continue
        local size="${DIR_SIZE["$path"]}"
        [ -z "$size" ] && continue
        local pct=$(( (size * 100 + total_size / 2) / total_size ))
        [ "$pct" -lt "$SHOW_THRESHOLD" ] && continue
        items+=("$size|$pct|$path")
    done <<< "$children"

    # 如果没有符合条件的子目录,直接返回
    [ ${#items[@]} -eq 0 ] && return

    local sorted=()
    mapfile -t sorted < <(printf '%s\n' "${items[@]}" | grep -v '^$' | LC_ALL=C sort -t'|' -k1,1nr)

    local count=${#sorted[@]}
    local i=0

    for item in "${sorted[@]}"; do
        # 跳过空项
        [ -z "$item" ] && continue

        i=$((i + 1))
        local size="${item%%|*}"
        local rest="${item#*|}"
        local pct="${rest%%|*}"
        local path="${rest#*|}"

        # 防止空值导致的整数比较错误
        [ -z "$size" ] && continue
        [ -z "$pct" ] && pct=0
        [ -z "$path" ] && continue

        # 判断是否是最后一个
        local is_last=0
        [ "$i" -eq "$count" ] && is_last=1

        # 选择树形符号
        local branch
        local child_prefix
        if [ $is_last -eq 1 ]; then
            branch="└── "
            child_prefix="${prefix}    "
        else
            branch="├── "
            child_prefix="${prefix}│   "
        fi

        # 根据占比选择颜色
        local color="$CYAN"
        [[ "$pct" =~ ^[0-9]+$ ]] && [ "$pct" -ge 60 ] && color="$RED"
        [[ "$pct" =~ ^[0-9]+$ ]] && [ "$pct" -ge 40 ] && [ "$pct" -lt 60 ] && color="$YELLOW"

        # 显示目录名
        local name="${path##*/}"
        printf "${prefix}${branch}%-20s ${color}%8s  %3s%%${NC}\n" "$name" "$(human_size "$size")" "$pct"

        # 占用超过阈值则递归
        if [[ "$pct" =~ ^[0-9]+$ ]] && [ "$pct" -ge "$RECURSE_THRESHOLD" ]; then
            local sub_size="$size"

            # 检查子目录是否还有超过阈值的项(从缓存)
            local has_more=0
            local sub_children="${DIR_CHILDREN["$path"]}"
            if [ -n "$sub_children" ] && (( sub_size > 0 )); then
                while IFS= read -r sub_item_path; do
                    [ -z "$sub_item_path" ] && continue
                    local sub_item_size="${DIR_SIZE["$sub_item_path"]}"
                    [ -z "$sub_item_size" ] && continue
                    local sub_pct=$(( (sub_item_size * 100 + sub_size / 2) / sub_size ))
                    if [ "$sub_pct" -ge "$RECURSE_THRESHOLD" ]; then
                        has_more=1
                        break
                    fi
                done <<< "$sub_children"
            fi

            if [ "$has_more" -eq 1 ]; then
                # 继续递归
                analyze_directory "$path" "$child_prefix" "$sub_size"
            else
                # 已到最深层,显示 Top N
                printf "${child_prefix}\n"
                show_top_items "$path" "$child_prefix" "$sub_size"
            fi
        fi
    done
}

analyze_directories() {
    printf "${YELLOW}[1] 子目录占用分析 (>%d%%, 递归>%d%%)${NC}\n" "$SHOW_THRESHOLD" "$RECURSE_THRESHOLD"
    printf "────────────────────────────────────────────────────────\n"

    local start_time=$(date +%s)

    # 核心优化:一次构建索引
    build_dir_index "$TARGET_DIR"
    local total_size="${DIR_SIZE["$TARGET_DIR"]}"

    printf "    ${BOLD}%-20s %8s${NC}\n" "$TARGET_DIR" "$(human_size "$total_size")"

    analyze_directory "$TARGET_DIR" "    " "$total_size"

    printf "\n    耗时: $(($(date +%s) - start_time))秒\n\n"
}

# ==============================================================================
# [2] 大文件查找(优化:使用 awk 插入排序替代 sort | head)
# ==============================================================================
find_large_files() {
    printf "${YELLOW}[2] 大文件 Top ${TOP_N} (>${LARGE_FILE_LIMIT_MB}MB)${NC}\n"
    printf "────────────────────────────────────────────────────────\n"
    printf "    ${BOLD}%-50s  %-10s  %-10s  %-8s${NC}\n" "PATH" "SIZE" "DATE" "OWNER"
    printf "    %-50s  %-10s  %-10s  %-8s\n" "--------------------------------------------------" "----------" "----------" "--------"

    local start_time=$(date +%s)
    local count=0

    while IFS=$'\t' read -r size mtime owner path; do
        [ -z "$size" ] && continue
        count=$((count + 1))
        printf "    %-50s  ${RED}%-10s${NC}  %-10s  %-8s\n" \
            "$path" "$(human_size "$size")" "$mtime" "$owner"
    done < <(
        find "$TARGET_DIR" -xdev -type f -size +"${LARGE_FILE_LIMIT_MB}"M \
            -printf '%s\t%TY-%Tm-%Td\t%u\t%p\n' 2>/dev/null | \
        awk -v n="$TOP_N" -F '\t' '
            BEGIN { count=0 }
            {
                size=$1+0
                line=$0
                pos=count+1
                for (i=1; i<=count; i++) {
                    if (size > sizes[i]) { pos=i; break }
                }
                if (pos > n) next
                if (count < n) count++
                for (j=count; j>pos; j--) {
                    sizes[j]=sizes[j-1]
                    lines[j]=lines[j-1]
                }
                sizes[pos]=size
                lines[pos]=line
            }
            END { for (i=1; i<=count; i++) print lines[i] }
        '
    )

    [ $count -eq 0 ] && printf "    ${GREEN}无超过 ${LARGE_FILE_LIMIT_MB}MB 的文件${NC}\n"

    printf "\n    耗时: $(($(date +%s) - start_time))秒\n\n"
}

# ==============================================================================
# 主程序
# ==============================================================================
main() {
    printf "\n"
    printf "${BOLD}═══════════════════════════════════════════════════════════${NC}\n"
    printf "${BOLD}  磁盘分析: ${CYAN}%s${NC}  [%s]${NC}\n" "$TARGET_DIR" "$(date '+%Y-%m-%d %H:%M:%S')"
    printf "${BOLD}═══════════════════════════════════════════════════════════${NC}\n\n"

    analyze_directories
    find_large_files

    printf "${GREEN}完成${NC}\n\n"
}

main

2.2 使用方法

./disk_analyze.sh [目录] [大文件阈值MB]

2.3 参数说明

参数 默认值 描述
目录 当前目录 要分析的目标目录
大文件阈值 MB 1024 大文件筛选阈值(单位:MB)

2.4 使用示例

# 分析当前目录
./disk_analyze.sh

# 分析 /var 目录
./disk_analyze.sh /var

# 分析 /home 目录,查找超过 500MB 的文件
./disk_analyze.sh /home 500

3 效果展示

3.1 扫描进度

分析大目录时显示实时进度:

3.2 完整输出

4 颜色说明

  • 🔴 红色:占用 ≥60%,需重点关注
  • 🟡 黄色:占用 40%-60%,建议检查
  • 🔵 青色:占用 10%-40%,正常范围

5 环境要求

  • Bash 4.0+(需要关联数组支持)
  • GNU coreutils(du, find)
  • awk

6 注意事项

  • 脚本使用 -x 参数,不会跨越文件系统边界
  • 符号链接会被跟随(-L)
  • 需要对目标目录有读取权限

以上.

wechat_pay

微信

alipay

支付宝