在平常 SA 工作中,我们通常需要使用 grep、awk、jq 等命令对日志文件进行过滤。处理多个文件时,使用 for、while 进行循环处理,但是一般使用顺序执行,执行效率低下。放在后台运行,又无法控制并发数量,而且每次都要思索一下,怎样写一堆的命令来完成工作。
这里借用网上一哥们使用队列控制并发的想法,实现多进程并发通用脚本。
Bash Code
vim ccmd.sh
#!/bin/bash
# Author : Mason Chan
# Date : 2019-11-06
# Version : 0.1
################################################################################
# 控制并发进程的全局变量
################################################################################
# 并发数
cpus=$(grep -c ^processor /proc/cpuinfo)
MAXIMUM_PROCESS_CONCURRENCY=$(expr $cpus - 1)
# 存放正在运行的 PID 列表
PID_OF_RUNNING_PROCESS=()
# 运行进程数
NUM_OF_RUNNING_PROCESS=0
function set_maximum_process_count()
{
local user_count=$1
if [ $user_count -gt $MAXIMUM_PROCESS_CONCURRENCY ]; then
echo "Num of Concurrent Process is too large: [$user_count], expected should less than vCPU[$cpus]."
exit 1
fi
MAXIMUM_PROCESS_CONCURRENCY=$1
}
################################################################################
# 检测队列里的进程是否运行完毕,并更新进程运行状态
################################################################################
function check()
{
local LAST_PID=(${PID_OF_RUNNING_PROCESS[@]})
PID_OF_RUNNING_PROCESS=()
for pid in "${LAST_PID[@]}";do
if [[ -d "/proc/$pid" ]];then
PID_OF_RUNNING_PROCESS=(${PID_OF_RUNNING_PROCESS[@]} $pid)
fi
done
NUM_OF_RUNNING_PROCESS=${#PID_OF_RUNNING_PROCESS[@]}
}
################################################################################
# 帮助函数
################################################################################
function usage()
{
cat << EOF
Usage: sh $0 -c cmd -f files [-d]
-c cmd ,需要并发执行的命令,使用固定字符 FILE 指定需要处理文件列表
-f files ,需要处理文件列表,执行命令前会代替 cmd 中的 FILE,一个文件生成一个单独的命令
-d dry run,仅输出并发信息,脚本自动赋值,0 执行命令,1 仅打印命令(默认)
-m max concurrent process,最大并发进程数,默认 vCPU-1
此脚本用于并发处理文件,常与 wc、split 命令配合使用。脚本结果输出到 *.out。
e.g. 将一个大文件按行分割为多个小文件
wc -l all.log
split -l 10000 all.log
例一:while 读取文件并按行提取 json 数据
sh $0 -c "while read line; do echo \\\$line | jq '.ip'; done < FILE" -f "x*" -d
例二:zstd 解压文件
sh $0 -c "zstd -d FILE -f -o FILE.out" -f "*.zst" -d
例三:awk 提取 Nginx 的 POST 请求,并取第 6、24 列数据
sh $0 -c "awk '{if(\\\$5 == \"POST\") print \\\$6\"@@\"\\\$24}' FILE >FILE.csv" -f "*.zst.out" -d
例四:grep 过滤文档
sh $0 -c "grep -iE \"suggestWords\" FILE >FILE.csv" -f "*.zst.out" -d
Note 1. 在 -c 的命令中,使用固定字符 FILE 来这是一个文件;接着用 -f 指定文件路径,支持通配符
2. 在 -c 的命令中,特殊字符需要转义,特殊符号包括:双引号 ""、美元符号 $
3. -c 和 -f 的内容必须使用双引号括起来
4. -d 可以进行预览,请多多使用
EOF
}
################################################################################
# main
################################################################################
test $# -lt 2 && { usage; exit 1; }
# 参数解析,getopts 放函数里面运行是不生效的
cmd=""
files=()
dryrun=0
while getopts "f:c:m:d" OPTNAME; do
#echo $OPTNAME
#echo $OPTARG
case $OPTNAME in
c) cmd="$OPTARG";;
f) for file in $(ls $OPTARG); do files=(${files[@]} $file); done;;
m) set_maximum_process_count $OPTARG;;
d) dryrun=1;;
*) exit 1;;
esac
done
cat << EOF
################################################################################
Original Command : $cmd
Destination Files : ${files[@]}
Running Mode : $(test $dryrun -eq 0 && { echo "executable"; } || { echo "dry run"; }) [$dryrun]
Maximum Concurrent Process: $MAXIMUM_PROCESS_CONCURRENCY
################################################################################
EOF
# 同时启动多个进程,总进程数 = 总文件数
t=${#files[@]}
i=0
for file in ${files[@]}; do
((++i))
ccmd=$(echo $cmd | sed 's#FILE#'$file'#g')
output="${i}.out"
echo "[$i/$t] $ccmd >$output"
# 后台运行 ccmd
if [ $dryrun -eq 0 ]; then
eval $ccmd >$output &
fi
# 统计进程数
PID_OF_RUNNING_PROCESS=(${PID_OF_RUNNING_PROCESS[@]} $!)
NUM_OF_RUNNING_PROCESS=${#PID_OF_RUNNING_PROCESS[@]}
# 当正在运行的进程数 == 最大并发数时,进入等待状态,并更新进程运行状态
while [[ $NUM_OF_RUNNING_PROCESS -eq $MAXIMUM_PROCESS_CONCURRENCY ]]; do
check
sleep 0.1
done
done
wait
echo "Cost Time: ${SECONDS}s"
sh ccmd.sh
Usage: sh ccmd.sh -c cmd -f files [-d]
-c cmd ,需要并发执行的命令,使用固定字符 FILE 指定需要处理文件列表
-f files ,需要处理文件列表,执行命令前会代替 cmd 中的 FILE,一个文件生成一个单独的命令
-d dry run,仅输出并发信息,脚本自动赋值,0 执行命令,1 仅打印命令(默认)
-m max concurrent process,最大并发进程数,默认 vCPU-1
此脚本用于并发处理文件,常与 wc、split 命令配合使用。脚本结果输出到 *.out。
e.g. 将一个大文件按行分割为多个小文件
wc -l all.log
split -l 10000 all.log
例一:while 读取文件并按行提取 json 数据
sh ccmd.sh -c "while read line; do echo \$line | jq '.ip'; done < FILE" -f "x*" -d
例二:zstd 解压文件
sh ccmd.sh -c "zstd -d FILE -f -o FILE.out" -f "*.zst" -d
例三:awk 提取 Nginx 的 POST 请求,并取第 6、24 列数据
sh ccmd.sh -c "awk '{if(\$5 == \"POST\") print \$6\"@@\"\$24}' FILE >FILE.csv" -f "*.zst.out" -d
例四:grep 过滤文档
sh ccmd.sh -c "grep -iE \"suggestWords\" FILE >FILE.csv" -f "*.zst.out" -d
Note 1. 在 -c 的命令中,使用固定字符 FILE 来这是一个文件;接着用 -f 指定文件路径,支持通配符
2. 在 -c 的命令中,特殊字符需要转义,特殊符号包括:双引号 ""、美元符号 $
3. -c 和 -f 的内容必须使用双引号括起来
4. -d 可以进行预览,请多多使用
生产实际使用例子
场景:从多个 nginx 文件(zstd 压缩文件)里面提取 uri 和 post data。
文件准备
du -shc *.zst
31M 225_19.zst
24M 225_20.zst
27M 225_21.zst
27M 225_22.zst
31M 239_19.zst
24M 239_20.zst
27M 239_21.zst
27M 239_22.zst
31M 85_19.zst
24M 85_20.zst
27M 85_21.zst
27M 85_22.zst
319M total
解压文件
解压预览
sh ccmd.sh -c "zstd -d FILE -f -o FILE.out" -f "*.zst" -d
################################################################################
Original Command : zstd -d FILE -f -o FILE.out
Destination Files : 225_19.zst 225_20.zst 225_21.zst 225_22.zst 239_19.zst 239_20.zst 239_21.zst 239_22.zst 85_19.zst 85_20.zst 85_21.zst 85_22.zst
Running Mode : dry run [1]
Maximum Concurrent Process: 23
################################################################################
[1/12] zstd -d 225_19.zst -f -o 225_19.zst.out >1.out
[2/12] zstd -d 225_20.zst -f -o 225_20.zst.out >2.out
[3/12] zstd -d 225_21.zst -f -o 225_21.zst.out >3.out
[4/12] zstd -d 225_22.zst -f -o 225_22.zst.out >4.out
[5/12] zstd -d 239_19.zst -f -o 239_19.zst.out >5.out
[6/12] zstd -d 239_20.zst -f -o 239_20.zst.out >6.out
[7/12] zstd -d 239_21.zst -f -o 239_21.zst.out >7.out
[8/12] zstd -d 239_22.zst -f -o 239_22.zst.out >8.out
[9/12] zstd -d 85_19.zst -f -o 85_19.zst.out >9.out
[10/12] zstd -d 85_20.zst -f -o 85_20.zst.out >10.out
[11/12] zstd -d 85_21.zst -f -o 85_21.zst.out >11.out
[12/12] zstd -d 85_22.zst -f -o 85_22.zst.out >12.out
Cost Time: 0s
开始解压
单进程解压
time for file in `ls *.zst`; do zstd -d $file -f -o ${file}.log; done
225_19.zst : 207738867 bytes
225_20.zst : 161573441 bytes
225_21.zst : 181002508 bytes
225_22.zst : 182898385 bytes
239_19.zst : 207741015 bytes
239_20.zst : 161561520 bytes
239_21.zst : 180934060 bytes
239_22.zst : 182721923 bytes
85_19.zst : 207801849 bytes
85_20.zst : 161348415 bytes
85_21.zst : 180844584 bytes
85_22.zst : 182741311 bytes
real 0m9.225s
user 0m6.048s
sys 0m2.915s
du -shc *.zst.log
199M 225_19.zst.log
155M 225_20.zst.log
173M 225_21.zst.log
175M 225_22.zst.log
199M 239_19.zst.log
155M 239_20.zst.log
173M 239_21.zst.log
175M 239_22.zst.log
199M 85_19.zst.log
154M 85_20.zst.log
173M 85_21.zst.log
175M 85_22.zst.log
2.1G total
多进程并发解压
sh ccmd.sh -c "zstd -d FILE -f -o FILE.out" -f "*.zst"
################################################################################
Original Command : zstd -d FILE -f -o FILE.out
Destination Files : 225_19.zst 225_20.zst 225_21.zst 225_22.zst 239_19.zst 239_20.zst 239_21.zst 239_22.zst 85_19.zst 85_20.zst 85_21.zst 85_22.zst
Running Mode : executable [0]
Maximum Concurrent Process: 23
################################################################################
[1/12] zstd -d 225_19.zst -f -o 225_19.zst.out >1.out
[2/12] zstd -d 225_20.zst -f -o 225_20.zst.out >2.out
[3/12] zstd -d 225_21.zst -f -o 225_21.zst.out >3.out
[4/12] zstd -d 225_22.zst -f -o 225_22.zst.out >4.out
[5/12] zstd -d 239_19.zst -f -o 239_19.zst.out >5.out
[6/12] zstd -d 239_20.zst -f -o 239_20.zst.out >6.out
[7/12] zstd -d 239_21.zst -f -o 239_21.zst.out >7.out
[8/12] zstd -d 239_22.zst -f -o 239_22.zst.out >8.out
[9/12] zstd -d 85_19.zst -f -o 85_19.zst.out >9.out
[10/12] zstd -d 85_20.zst -f -o 85_20.zst.out >10.out
[11/12] zstd -d 85_21.zst -f -o 85_21.zst.out >11.out
[12/12] zstd -d 85_22.zst -f -o 85_22.zst.out >12.out
225_20.zst : 161573441 bytes
225_21.zst : 181002508 bytes
85_22.zst : 182741311 bytes
225_19.zst : 207738867 bytes
239_20.zst : 161561520 bytes
85_20.zst : 161348415 bytes
225_22.zst : 182898385 bytes
239_22.zst : 182721923 bytes
239_21.zst : 180934060 bytes
85_21.zst : 180844584 bytes
239_19.zst : 207741015 bytes
85_19.zst : 207801849 bytes
Cost Time: 2s
du -shc *.zst.out
199M 225_19.zst.out
155M 225_20.zst.out
173M 225_21.zst.out
175M 225_22.zst.out
199M 239_19.zst.out
155M 239_20.zst.out
173M 239_21.zst.out
175M 239_22.zst.out
199M 85_19.zst.out
154M 85_20.zst.out
173M 85_21.zst.out
175M 85_22.zst.out
2.1G total
耗时对比:9.225s VS 2s
提取 nginx 日志的 uri 和 post data
单进程提取
time for file in `ls *.zst.log`; do awk '{if($5 == "POST") print $6"@@"$24}' $file >${file}.csv; done
real 0m16.513s
user 0m12.967s
sys 0m3.448s
du -shc *.zst.log.csv
110M 225_19.zst.log.csv
83M 225_20.zst.log.csv
94M 225_21.zst.log.csv
95M 225_22.zst.log.csv
110M 239_19.zst.log.csv
83M 239_20.zst.log.csv
94M 239_21.zst.log.csv
95M 239_22.zst.log.csv
110M 85_19.zst.log.csv
83M 85_20.zst.log.csv
94M 85_21.zst.log.csv
95M 85_22.zst.log.csv
1.2G total
多进程并发提取
提取预览
sh ccmd.sh -c "awk '{if(\$5 == \"POST\") print \$6\"@@\"\$24}' FILE >FILE.csv" -f "*.zst.out" -d
################################################################################
Original Command : awk '{if($5 == "POST") print $6"@@"$24}' FILE >FILE.csv
Destination Files : 225_19.zst.out 225_20.zst.out 225_21.zst.out 225_22.zst.out 239_19.zst.out 239_20.zst.out 239_21.zst.out 239_22.zst.out 85_19.zst.out 85_20.zst.out 85_21.zst.out 85_22.zst.out
Running Mode : dry run [1]
Maximum Concurrent Process: 23
################################################################################
[1/12] awk '{if($5 == "POST") print $6"@@"$24}' 225_19.zst.out >225_19.zst.out.csv >1.out
[2/12] awk '{if($5 == "POST") print $6"@@"$24}' 225_20.zst.out >225_20.zst.out.csv >2.out
[3/12] awk '{if($5 == "POST") print $6"@@"$24}' 225_21.zst.out >225_21.zst.out.csv >3.out
[4/12] awk '{if($5 == "POST") print $6"@@"$24}' 225_22.zst.out >225_22.zst.out.csv >4.out
[5/12] awk '{if($5 == "POST") print $6"@@"$24}' 239_19.zst.out >239_19.zst.out.csv >5.out
[6/12] awk '{if($5 == "POST") print $6"@@"$24}' 239_20.zst.out >239_20.zst.out.csv >6.out
[7/12] awk '{if($5 == "POST") print $6"@@"$24}' 239_21.zst.out >239_21.zst.out.csv >7.out
[8/12] awk '{if($5 == "POST") print $6"@@"$24}' 239_22.zst.out >239_22.zst.out.csv >8.out
[9/12] awk '{if($5 == "POST") print $6"@@"$24}' 85_19.zst.out >85_19.zst.out.csv >9.out
[10/12] awk '{if($5 == "POST") print $6"@@"$24}' 85_20.zst.out >85_20.zst.out.csv >10.out
[11/12] awk '{if($5 == "POST") print $6"@@"$24}' 85_21.zst.out >85_21.zst.out.csv >11.out
[12/12] awk '{if($5 == "POST") print $6"@@"$24}' 85_22.zst.out >85_22.zst.out.csv >12.out
Cost Time: 0s
提取执行
sh ccmd.sh -c "awk '{if(\$5 == \"POST\") print \$6\"@@\"\$24}' FILE >FILE.csv" -f "*.zst.out"
################################################################################
Original Command : awk '{if($5 == "POST") print $6"@@"$24}' FILE >FILE.csv
Destination Files : 225_19.zst.out 225_20.zst.out 225_21.zst.out 225_22.zst.out 239_19.zst.out 239_20.zst.out 239_21.zst.out 239_22.zst.out 85_19.zst.out 85_20.zst.out 85_21.zst.out 85_22.zst.out
Running Mode : executable [0]
Maximum Concurrent Process: 23
################################################################################
[1/12] awk '{if($5 == "POST") print $6"@@"$24}' 225_19.zst.out >225_19.zst.out.csv >1.out
[2/12] awk '{if($5 == "POST") print $6"@@"$24}' 225_20.zst.out >225_20.zst.out.csv >2.out
[3/12] awk '{if($5 == "POST") print $6"@@"$24}' 225_21.zst.out >225_21.zst.out.csv >3.out
[4/12] awk '{if($5 == "POST") print $6"@@"$24}' 225_22.zst.out >225_22.zst.out.csv >4.out
[5/12] awk '{if($5 == "POST") print $6"@@"$24}' 239_19.zst.out >239_19.zst.out.csv >5.out
[6/12] awk '{if($5 == "POST") print $6"@@"$24}' 239_20.zst.out >239_20.zst.out.csv >6.out
[7/12] awk '{if($5 == "POST") print $6"@@"$24}' 239_21.zst.out >239_21.zst.out.csv >7.out
[8/12] awk '{if($5 == "POST") print $6"@@"$24}' 239_22.zst.out >239_22.zst.out.csv >8.out
[9/12] awk '{if($5 == "POST") print $6"@@"$24}' 85_19.zst.out >85_19.zst.out.csv >9.out
[10/12] awk '{if($5 == "POST") print $6"@@"$24}' 85_20.zst.out >85_20.zst.out.csv >10.out
[11/12] awk '{if($5 == "POST") print $6"@@"$24}' 85_21.zst.out >85_21.zst.out.csv >11.out
[12/12] awk '{if($5 == "POST") print $6"@@"$24}' 85_22.zst.out >85_22.zst.out.csv >12.out
Cost Time: 2s
du -shc *.zst.out.csv
110M 225_19.zst.out.csv
83M 225_20.zst.out.csv
94M 225_21.zst.out.csv
95M 225_22.zst.out.csv
110M 239_19.zst.out.csv
83M 239_20.zst.out.csv
94M 239_21.zst.out.csv
95M 239_22.zst.out.csv
110M 85_19.zst.out.csv
83M 85_20.zst.out.csv
94M 85_21.zst.out.csv
95M 85_22.zst.out.csv
1.2G total
耗时对比:16.513s VS 2S
网友评论