Linux 查找大文件、大目录,解决磁盘空间不足的问题
最近阿里云服务器频繁报警,报警原因就是服务器的内存使用率又高达 98%,云盘使用高达 93%。
云盘空间不足,那就删除文件,它相比解决内存不足要简单,今天我们先来解决云盘空间不足的问题。 而解决此问题,无非就是找大文件和大量小文件,比如:
- 没有按日期存储的日志文件,如果一直不删除该日志文件,那么它会越来越大。
- 按日期存储的日志文件,没有按日期删除的话,单个文件可能不大,但是大量的单个文件就不小了。
首先需要你熟悉 df、du、find 这几个命令。
df
df(英文全拼:disk free) 命令用于显示目前在 Linux 系统上的文件系统磁盘使用情况统计。
1 | $ df |
一般/dev/vda1
为系统盘,像vdb、vdc
为数据盘。
-h
我们发现 df
输出的格式没有单位,可读性太低了。使用 -h
参数可解决。-h
或 –human-readable 以 K,M,G 为单位,提高信息的可读性。
1 | $ df -h |
du
du (英文全拼:disk usage)命令用于显示目录或文件的大小。
-h
1 | # admin @ Main in ~/.oh-my-zsh/.git on git:master o [22:17:56] |
从输出可以看出du是显示目录和子目录所占用的磁盘空间。
1 | $ du -h .oh-my-zsh/.github/ |
-a
-a
, --all
显示所有目录(包括隐藏目录)和文件(包括隐藏文件)大小。
1 | $ du -ha .oh-my-zsh/.github/ |
–max-depth=<目录层数>
比如最多显示两层:
1 | $ du -h --max-depth=2 |
sort
你有没有发现上一条命令文件列表的目录大小并不是按照从大到小或从小到大的排序。
使用sort
命令即可排序。
使用|
来分隔不同的命令。
1 | $ du -h --max-depth=2 | sort |
从上图可以看出,只是输入sort是不行的,文件大小并没有排序。
是的,必须加入sort参数才行。
下面说下本文会用到的sort参数:
-r
(–reverse) 以相反的顺序来排序。-h
( –human-numeric-sort):使用易读性数字(例如:2K、1G),默认从小到大排序。
1 | $ du -h --max-depth=2 | sort -rh |
-s
-s
或–summarize 仅显示总计。只显示指定目录或当前目录的大小。
1 | # admin @ Main in ~/.oh-my-zsh/.git on git:master o [22:34:51] |
1 | $ du -sh ~ |
find
find
用于查找到的子目录和文件全部进行显示(包括隐藏目录和文件)。
1 | $ find ~/.oh-my-zsh/.github/ |
-name
-name
按名称查找当前目录及子目录的目录或文件。
- 查找当前目录,
.
可以省略掉; - 查找其他目录,将
.
替换为路径
1 | $ find -name 'pack*' |
-type
-type f
:查找普通文件。1
2
3
4
5
6
7
8$ find -name '*pack*' -type f
./packed-refs
./objects/pack/pack-ce42a3aed83714e763869bda4d0f5cc5682dfc9a.pack
./objects/pack/pack-ce42a3aed83714e763869bda4d0f5cc5682dfc9a.idx
./objects/pack/pack-fc84bdb90600217e79fb1d4cba8960f01e16687e.idx
./objects/pack/pack-fc84bdb90600217e79fb1d4cba8960f01e16687e.pack
./objects/pack/pack-4b7d6b93ae069ad2020588466c22f0fef836e042.idx
./objects/pack/pack-4b7d6b93ae069ad2020588466c22f0fef836e042.pack-type d
:查找目录。1
2$ find -name '*pack*' -type d
./objects/pack
-ctime
-ctime n
: 在过去n天内被修改过的文件。
将当前目录及其子目录下所有最近 30 天内更新过的文件列出:
1 | $ find ~ -ctime -30 |
-size
-size
:查找指定大小的文件。
查找大于100M的文件:
1
2$ find ~ -size +80M
/home/admin/.forever/6n_r.log查找大于 40M 小于80M 的文件:
1
2
3
4
5$ find ~ -size +40M -size -80M
/home/admin/.forever/H2sJ.log
/home/admin/.forever/iula.log
/home/admin/.vscode-server/bin/899d46d82c4c95423fb7e10e68eba52050e30ba3/node
/home/admin/.vscode-server/bin/c3f126316369cd610563c75b1b1725e0679adfb3/node
查找到文件数量
1 | $ find . -name '*2022-12*' | wc -l |
Linux系统中wc(Word Count)命令的功能为统计指定文件中的字节数、字数、行数,并将统计结果显示输出。如果没有给出文件名,则从标准输入读取。
显示文件大小并排序
xargs
xargs(英文全拼: eXtended ARGuments)是给命令传递参数的一个过滤器,也是组合多个命令的一个工具。
xargs 可以将管道或标准输入(stdin)数据转换成命令行参数,也能够从文件的输出中读取数据。之所以能用到这个命令,关键是由于很多命令不支持|管道来传递参数。
1
2 find -name "*2022-12*" | ls -l #这个命令是错误的
find -name "*2022-12*" | xargs ls -l #这样才是正确的
我想到的第一种方法就是通过下面的命令。
1 | find -name "*2022-12*" | xargs du -csh | sort -rh |
它在文件或目录没有空格时是正常的,但是文件或目录有空格就有问题了。
因为 xargs 识别字符段的标识是空格或者换行符,所以如果一个文件名里有空格或者换行符,xargs就会把它识别成两个字符串,自然就出错了。
1 | # admin @ Main in /ktt/common/apache-tomcat-7.0.62/logs [17:35:02] |
要解决此问题有两种方法:
方法一
-print0
用于 find
和 -0
用于 xargs
:
find -print0
表示在 find 的每一个结果之后加一个 NULL
字符,而不是默认加一个换行符。find
默认在每一个结果后加一个 ‘\n’,所以输出结果是一行一行的。当使用了 -print0
之后,就变成一行了。
1 | $ find -name "*2022-12*" -print0 |
**xargs -0
**表示 xargs
用 NULL
来作为分隔符。这样前后搭配就不会出现空格和换行符的错误了。选择 NULL
做分隔符,是因为一般编程语言把 NULL
作为字符串结束的标志,所以文件名不可能以 NULL
结尾,这样确保万无一失。
1 | $ find -name "*2022-12*" -print0 | xargs -0 du -csh | sort -rh |
方法二
告诉 xargs 精确的分隔符来处理文件名中的空格。
1 | $ find -name "*2022-12*" | xargs -d '\n' du -sh | sort -rh |
-exec
1 | $ find -name "*2022-12*" -exec du -sh {} \; | sort -rh |
显示文件总大小
1 | $ find . -name '*2022-12*' | xargs du -ch | tail -1 |
查找大文件
例如查找根目录下面大于 300M 的文件,包含文件属性,并且从大到小排序:
1 | $ sudo find / -type f -size +300M | xargs ls -lhS |
只显示文件大小和文件路径:
1 | $ sudo find / -type f -size +300M | xargs du -h | sort -hr |
查找大目录
直接从系统根目录开始查找(操作系统根目录需要 root 权限)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14$ sudo du -h --max-depth=1 / | sort -hr
[sudo] admin 的密码:
du: 无法访问"/proc/16916/task/16916/fd/4": 没有那个文件或目录
du: 无法访问"/proc/16916/task/16916/fdinfo/4": 没有那个文件或目录
du: 无法访问"/proc/16916/fd/3": 没有那个文件或目录
du: 无法访问"/proc/16916/fdinfo/3": 没有那个文件或目录
52G /
38G /var
8.6G /ktt
4.5G /usr
918M /home
406M /root
185M /run
108M /boot好家伙,var 目录占了 38G。
先看下 var 目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21$ sudo du -ha /var/ | sort -hr | head -20
38G /var/
36G /var/lib
27G /var/lib/mysql
18G /var/lib/mysql/mysql
8.7G /var/lib/mysql/mysql/slow_log.CSV
8.6G /var/lib/mysql/ibdata1
8.5G /var/lib/mysql/mysql/slow_log.CSN
7.7G /var/lib/docker
4.4G /var/lib/docker/volumes
3.3G /var/lib/docker/devicemapper/devicemapper/data
3.3G /var/lib/docker/devicemapper/devicemapper
3.3G /var/lib/docker/devicemapper
1.6G /var/lib/mongo
888M /var/log
686M /var/log/mongodb/mongod.log
686M /var/log/mongodb
507M /var/lib/docker/volumes/wekan2_wekan-db/_data
507M /var/lib/docker/volumes/wekan2_wekan-db
496M /var/lib/mysql/mysql-bin.000005
411M /var/cachehead
命令用来来显示列表前几个,默认显示前 10 个,要查看前 20 个,请使用head -20
。从输出列表可以看出 mysql 的 slow_log.CSV、ibdata1、slow_log.CSN 这三个文件就占了 25.8G。然后其他的就是 docker 和 mongo,这些都与数据相关,先不删除。
再看下 ktt 目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21$ sudo du -ha /ktt/ | sort -hr | head -20
8.6G /ktt/
6.4G /ktt/common
3.6G /ktt/common/apache-tomcat-7.0.62
3.5G /ktt/common/apache-tomcat-7.0.62/logs
2.8G /ktt/common/apache-tomcat-7.0.62/logs/catalina.out
2.1G /ktt/common/logs/KTTMsgServer.log
2.1G /ktt/common/logs
604M /ktt/common/download
594M /ktt/NewNS
593M /ktt/JavaAPI
326M /ktt/www
314M /ktt/Ktt Server Arch
225M /ktt/NewNS/YS-LoraHomeNS
224M /ktt/NewNS/YS-LoraHomeNS1
200M /ktt/common/download/node-v4.2.4-linux-x64
190M /ktt/common/apache-tomcat-7.0.62-test
186M /ktt/common/download/node-v6.9.2-linux-x64
183M /ktt/Ktt Server Arch/BaseCloudNode
161M /ktt/Ktt Server Arch/BaseCloudNode/node_modules
158M /ktt/YoSmartAPI1
2
3
4
5
6
7
8
9
10
11
12
13
14$ ls -lhS /ktt/common/apache-tomcat-7.0.62/logs/
总用量 3.5G
-rw-r--r-- 1 root root 2.8G 12月 3 11:49 catalina.out
-rw-r--r-- 1 root root 4.6M 4月 30 2022 localhost_access_log.2022-04-30.txt
-rw-r--r-- 1 root root 4.4M 4月 30 2022 localhost_access_log.2022-04-29.txt
-rw-r--r-- 1 root root 4.3M 5月 1 2022 localhost_access_log.2022-05-01.txt
...
-rw-r--r-- 1 root root 123 8月 11 09:45 catalina.2022-08-11.log
-rw-r--r-- 1 root root 123 9月 22 05:27 catalina.2022-09-22.log
-rw-r--r-- 1 root root 123 11月 23 14:46 catalina.2022-11-23.log
-rw-r--r-- 1 root root 123 11月 27 15:58 catalina.2022-11-27.log
...
-rw-r--r-- 1 root root 0 4月 26 2022 host-manager.2022-04-26.log
-rw-r--r-- 1 root root 0 4月 26 2022 manager.2022-04-26.log
清理 Tomcat log
catalina.out
catalina.out 其实是 tomcat 的标准输出(stdout)和标准出错(stderr),这是在 tomcat 的启动脚本里指定的,如果没有修改的话 stdout 和 stderr 会重定向到这里。所以我们在应用里使用 System.out 打印的东西都会到这里来。另外,如果我们在应用里使用其他的日志框架,配置了向 Console 输出的,则也会在这里出现。比如以 logback 为例,如果配置 ch.qos.logback.core.ConsoleAppender 则会输出到 catalina.out 里。
1 | # admin @ Main in ~ [12:46:35] |
7 个月该文件就增长了 2.8G,运行 5 年将占用 2.8/7125=24G 左右。
随着每天业务的增长,Tomcat 的 catalina.out 日志变得越来越大,占用磁盘空间不说。要查看某个时候的日志的时候,庞大的日志让你顿时无从下手。一般通过 cronlog 切割软件来切割日志,切割后的日志,还可以定期清理掉久远的日志。当然操作也没那么简单,因此不是本篇文章的重点,下面我讲解如何以最简单的方式来清理日志。
catalina.YYYY-MM-DD.log
catalina.{yyyy-MM-dd}.log 是 Tomcat 自己运行的一些日志,主要记录 Tomcat 在启动和暂停时的运行内容。
localhost.YYYY-MM-DD.log
localhost.{yyyy-MM-dd}.log 主要是应用初始化(listener, filter, servlet)未处理的异常最后被 Tomcat 捕获而输出的日志,它也是包含 Tomcat 的启动和暂停时的运行日志,但它没有 catalina.YYYY-MM-DD.log 日志全。
localhost_access_log.YYYY-MM-DD.txt
Tomcat 的请求访问日志,请求的时间,请求的类型,请求的资源和返回的状态码都有记录。配置这个日志非常有必要,可以让我们清楚的看清请求的状况。
删除指定天数前的 log
查看 10 天前的文件大小和总大小:
1 | $ find . -ctime +10 -name '*' | xargs du -ch |
删除 10 天前的文件:
1 | $ find . -ctime +10 -name '*' -print -delete |
*
指任何类型的文件,当然你也可以指定文件后缀,如 *.txt。-print
打印删除的文件,-delete
目录和文件都会删除。
时间条件:
- amin n 查找n分钟以前被访问过的所有文件。
- atime n: 查找n天以前被访问过的所有文件。
- cmin n: 查找n分钟以前文件状态被修改过的所有文件。
- ctime n: 查找n天以前文件状态被修改过的所有文件。
- mmin n: 查找n分钟以前文件内容被修改过的所有文件。
- mtime n: 查找n天以前文件内容被修改过的所有文件。
清空 catalina.out 文件
为什么不是删除 catalina.out 文件,因为该文件如果正在被 Tomcat 进程操作,导致虽然删除了该文件,但文件对应的指针部分由于进程锁定,并未从 meta-data 中清除,而由于指针并未被删除,那么系统内核就认为文件并未被删除,因此空间不会释放。
Linux 下文件的存储机制和存储结构:
一个文件在文件系统中的存放分为两个部分:数据部分和指针(inode)部分,指针位于文件系统的 meta-data 中,数据被删除后,这个指针就从 meta-data 中清除了,而数据部分存储在磁盘中,数据对应的指针从 meta-data 中清除后,文件数据部分占用的空间就可以被覆盖并写入新的内容,之所以出现删除catalina.out 文件后,空间还没释放,就是因为 tomcat 进程还在一直向这个文件写入内容,导致虽然删除了 catalina.out 文件,但文件对应的指针部分由于进程锁定,并未从 meta-data 中清除,而由于指针并未被删除,那么系统内核就认为文件并未被删除,因此通过df命令查询空间并未释放也就不足为奇了。
清空文件使用如下命令:
1 | cat /dev/null > file |
如果你通过 rm 删除了 catalina.out,但是发现磁盘空间没有释放,那就只能重启 Tomcat 来解决了。
log 定期清理
通过将上面的清理命令编写为一个 Shell 脚本,然后创建一个 crontab 任务,该任务定期执行该 Shell 脚本。即可实现 log 定期清理。
编写 delete-tomcat-log.sh 脚本。
delete-tomcat-log.sh 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# author gan
echo "`date` 开始处理 Tomcat log 文件..."
echo ''
echo '处理前磁盘空间:'
df -h
echo ''
echo '当前工作目录:'
pwd
echo '切换到工作目录到 /ktt/common/apache-tomcat-7.0.62/logs'
cd /ktt/common/apache-tomcat-7.0.62/logs3
echo '当前工作目录:'
pwd
echo ''
echo "查看 10 天前的文件大小和总大小:"
find -ctime +10 | xargs -d '\n' du -csh | sort -rh
echo "10 天前的文件数量:"
find . -ctime +10 | wc -l
echo "删除 10 天前的文件:"
find . -ctime +10 -print -delete
echo "查看 10 天前的文件大小和总大小:"
find -ctime +10 | xargs -d '\n' du -csh | sort -rh
echo ''
filename=catalina.out
echo '清空前 catalina.out 属性:'
ls -lh $filename
filesize=`ls -l $filename | awk '{ print $5 }'`
maxsize=$((200*1024*1024))
if [ $filesize -gt $maxsize ]
then
echo "$filename 文件大小 `expr $filesize / 1024 / 1024`MB 大于 `expr $maxsize / 1024 / 1024`MB,清空该日志。"
echo "清空 catalina.out ..."
echo -n '' > ./catalina.out # -n 不输出换行符。
echo '清空后 catalina.out 属性:'
ls -lh ./catalina.out
# mv media.log media"`date +%Y-%m-%d_%H:%M:%S`".log
else
echo "$filename 文件大小 `expr $filesize / 1024 / 1024`MB 小于 `expr $maxsize / 1024 / 1024`MB,不清空该日志。"
fi
echo ''
echo '处理后磁盘空间:'
df -h
echo ''
echo "`date` 完成处理 Tomcat log 文件。"使脚本具有执行权限。
1
chmod +x ./delete-tomcat-log.sh
进入编辑 crontab 任务列表
1
sudo crontab -e
添加任务。
1
0 2 5 * * /ktt/crons/delete-tomcat-log.sh &>> /ktt/crons/delete-tomcat-log.log
0 2 5 * *
指每个月 5 号 2 点 0 分执行任务。&
指将标准输出和标准错误输出都重定向到 delete-tomcat-log.log 文件。>
指覆盖写入到文件。>>
指追加写入到文件。
参考
Linux 查找大文件、大目录,解决磁盘空间不足的问题