合普知识库
柔彩主题三 · 更轻盈的阅读体验

shell脚本运行慢怎么办

发布时间:2025-12-09 15:45:32 阅读:334 次

检查脚本中的循环和重复操作

有时候一个看似简单的 shell 脚本跑得特别慢,问题可能出在循环里。比如你在处理上千个文件时,每轮都调用外部命令 like findgrep,就会不断启动新进程,拖慢整体速度。

举个例子,下面这种写法就很常见但效率低:

for file in *.log; do
    grep "ERROR" $file >> errors.log
done

每次循环都会调用一次 grep,换成一行命令就能省下大量开销:

grep "ERROR" *.log > errors.log

避免频繁的磁盘读写

如果脚本一直在读写临时文件,比如每次处理一点数据就 echo >> temp.txt,这种小操作积少成多,会成为瓶颈。尽量把数据缓存在变量或管道中,减少 IO 次数。

比如要统计多个日志中某个字段的总和,别这么写:

for log in access_*.log; do
    awk '{sum += $10} END {print sum}' $log >> tmp_sums
done
awk '{total += $1} END {print total}' tmp_sums

直接一条 awk 就能搞定:

awk '{sum += $10} END {print sum}' access_*.log

用内置功能代替外部命令

shell 本身支持一些字符串处理,比如截取、替换,用 ${var#*pattern} 比调用 sedcut 快得多。特别是循环里,调一次外部命令的代价远高于内置操作。

比如提取文件名后缀,别总是依赖 basenameawk

filename="data_2024.txt"
ext=${filename##*.}

这样更快,也不依赖外部工具。

并行处理提升效率

有些任务彼此独立,比如压缩多个大文件,一个个来太耗时。可以用 & 放到后台并发执行,控制好数量别把系统拖垮就行。

max_jobs=4
for file in *.txt; do
    gzip "$file" &

    # 控制并发数
    if (( $(jobs -r | wc -l) >= max_jobs )); then
        wait -n
    fi
done
wait

这样同时最多跑 4 个 gzip,充分利用 CPU 空闲时间。

用更高效的工具替代部分逻辑

如果脚本里大量文本处理靠 while read 一行行啃,速度很难提上去。这时候不如交给 awkperl 一把梭。比如要解析 CSV 并按列过滤,用 awk 几秒完事,shell 循环可能要几十秒。

原写法:

while IFS=, read name age city; do
    if [[ $age -gt 30 ]]; then
        echo $name
    fi
done < users.csv

换成:

awk -F, '$2 > 30 {print $1}' users.csv

看看是不是 I/O 等待卡住了

运行脚本时用 tophtop 观察,如果 CPU 使用率很低,但脚本就是不动,大概率是卡在磁盘或网络读取上。比如从远程挂载的 NFS 目录读大量小文件,延迟会很明显。

可以先用 iotop 看看哪个进程在疯狂读写,再决定是否本地缓存数据或优化访问方式。

启用 shell 优化选项

Bash 有个 lastpipe 选项,在非交互模式下能让最后一个管道元素在主线程运行,避免子 shell 带来的变量作用域问题,有时也能省点开销。

set +m  # 关闭作业控制
shopt -s lastpipe
echo "1 2 3" | while read a b c; do
    value=$a
done
echo $value  # 此时 value 能拿到值

用 time 和 strace 定位瓶颈

最实在的办法是动手测。用 time 包一层脚本,看总共花了多少时间。

time ./slow_script.sh

如果发现用户态时间(user)高,说明计算密集;系统态(sys)高,可能是系统调用太多。再用 strace 跟一下:

strace -c ./slow_script.sh

输出会告诉你哪些系统调用最频繁,比如一堆 openatstat,那就知道该优化文件访问了。