美文网首页Bash Coding
Bash Shell 并发任务

Bash Shell 并发任务

作者: MasonChan | 来源:发表于2019-11-06 15:50 被阅读0次

在平常 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

相关文章

  • Bash Shell 并发任务

    在平常 SA 工作中,我们通常需要使用 grep、awk、jq 等命令对日志文件进行过滤。处理多个文件时,使用 f...

  • chapter 3.基本的bash shell命令

    基本的bash shell命令 启动shell 大多数Linux默认的shell都为GNU bash shell/...

  • Homebrew相关问题

    查看当前shell echo $SHELL zsh切换bash chsh -s /bin/bash bash切换z...

  • shell & bash基础命令及巧用

    shell与bash脚本的区别shell是Linux基础命令解释器bash(Bourne Again shell)...

  • Shell补课

    更改启动shell chsh -s /bin/bash shell目录文件 /etc/shells 区别.bash...

  • shell 编程学习

    当前shell执行命令。./或者source 新建shell:/bin/bash ./file.sh bash $...

  • getopts 解析bash 命令行参数

    getopts 解析bash 命令行参数 Shell脚本中的一项常见任务是解析命令行参数。 Bash提供了内置函数...

  • Bash

    主要概念 Linux默认的shell是bash Shell bash Shell 可以交互使用,或者作为一种强大的...

  • Day-2初识linux

    1.bash shell 是什么? bash shell 是一个命令解释器,用户输入命令之后,通过bash she...

  • bash环境配置文件

    longin shell 输入用户名密码取得的bash nologin shell bash的子进程 ./etc/...

网友评论

    本文标题:Bash Shell 并发任务

    本文链接:https://www.haomeiwen.com/subject/skmubctx.html