GNU Parallel
GNU Parallel 是一个用于并行执行 Shell 命令的工具,能够利用 CPU 的多核特性,将原本需要串行执行的任务并行化处理,从而极大地提高执行效率。特别适用于批量文件处理、大规模数据转换、网络请求以及计算密集型任务。
parallel [选项] "command {}" ::: arg1 arg2 ...# 或者通过管道cat args.txt | parallel [选项] "command {}"选项:控制并发数、日志、远程执行等行为command:要运行的命令模板::::分隔符,后面紧跟参数列表(类似于标准输入){}:占位符,会被替换为具体的参数
如果命令中没有包含
{},Parallel 默认会在命令末尾追加参数,建议显式写出{}以增加可读性。
Parallel 的核心在于任务调度与输出缓冲,它不是简单地后台运行命令,而是作为一个作业管理器存在。
Parallel 的工作过程可以分为三个阶段:
step1 输入解析与分发
- 读取标准输入(Stdin)或命令行参数(
:::) - 根据指定的并发数(
-j)建立作业队列
step2 并行执行
- 启动子进程:根据 CPU 核心数或用户设定,同时运行多个命令实例
- 替换参数:将输入行动态填充到命令模板中的
{}位置
step3 输出聚合
- 缓冲输出:Parallel 会捕获每个作业的标准输出(stdout)和错误输出(stderr)
- 原子打印:只有当一个作业彻底完成后,才将其输出一次性打印到屏幕,保证多线程环境下输出不会乱序交织
工作流程图:
INPUT (文件列表/参数) |[ 任务分发器 (Scheduler) ] <- 设定并发数 (-j) / | \Core1 Core2 Core3 (并行处理) | | |Buff1 Buff2 Buff3 (输出缓冲) \ | /[ 输出聚合器 (Aggregator) ] |OUTPUT (整洁有序的输出)(1) 智能核心利用
默认情况下,Parallel 会检测当前机器的 CPU 物理核心数,并以此作为并发度。
(2) 替换字符串
类似于 awk 的 $1,Parallel 使用 {} 及其变体来代表当前的输入参数。
(3) 输出保序(可选)
- 默认情况下,谁先做完谁先输出
- 使用
-k时,Parallel 会暂存先完成的任务结果,强制按输入顺序输出
Parallel 有一套强大的替换字符串,用于在命令中灵活地处理文件名、路径和扩展名。
假设当前输入参数为 data/archive.tar.gz:
| 符号 | 含义 | 对应示例值 |
|---|---|---|
{} |
完整输入行 | data/archive.tar.gz |
{.} |
去掉扩展名 | data/archive.tar |
{/} |
基名 | archive.tar.gz |
{//} |
目录名 | data |
{/.} |
基名 + 去掉扩展名 | archive.tar |
{#} |
作业序列号 | 1(如果是第一个任务) |
{%} |
作业槽位号 | 1(如果在第 1 个 CPU 核心上运行) |
替换符示例:
# {} 完整参数parallel echo {} ::: A B C
# {.} 格式转换:将所有 .png 转换为 .jpgls *.png | parallel convert {} {.}.jpg
# {/} 提取文件名:将 /var/log/ 下的文件复制到当前目录ls /var/log/*.log | parallel cp {} ./backup-{/}
# {#} 生成序号:给输出文件编号ls *.jpg | parallel mv {} img_{#}.jpg| 选项 | 含义与作用 |
|---|---|
-j N |
并发数;N 可以是数字或百分比(如 100%),默认是 CPU 核心数 |
-k |
保持顺序(Keep Order);强制输出顺序与输入顺序一致 |
--dry-run |
空运行;只打印将要执行的命令,不实际运行 |
--bar |
显示进度条;显示完成度、速度和预计剩余时间 |
--joblog file |
记录日志;将每个任务的退出码、运行时间记录到文件 |
--resume |
断点续传;配合 --joblog 使用,跳过已成功的任务 |
--pipe |
分块处理;将输入(通常是大文件)分块后传给命令 |
-S host |
远程执行(SSH);在远程服务器上运行命令 |
选项示例:
# -j:限制同时只跑 2 个任务ls *.txt | parallel -j 2 gzip {}
# --dry-run:在执行删除或覆盖操作前先预览find . -name "*.tmp" | parallel --dry-run rm {}
# --bar:显示进度条seq 1000 | parallel -j 20 --bar sleep 0.1
# --joblog & --resume:任务管理与断点续传ls *.mp4 | parallel --joblog my.log ffmpeg -i {} {.}.avi# 中断后再次运行,自动跳过已完成的任务ls *.mp4 | parallel --joblog my.log --resume ffmpeg -i {} {.}.avi输入源与组合
Section titled “输入源与组合”多源输入(笛卡尔积)
使用多个 ::: 传入多组参数:
# 类似于:for x in 1 2; do for y in A B; do echo $x $y; done; doneparallel echo {1} {2} ::: 1 2 ::: A B输出:
1 A1 B2 A2 B对应链接(类似 Python 的 zip)
使用 :::+ 实现一一对应:
parallel echo {1} {2} ::: 1 2 :::+ A B输出:
1 A2 B文件作为输入
使用 :::: 直接读取文件内容作为参数:
parallel ping -c 1 {} :::: hosts.txt使用重定向、管道、命令链
Section titled “使用重定向、管道、命令链”在 Parallel 中使用管道符 (|)、重定向符 (>, >>) 或命令分隔符 (;, &&) 时,必须引用整个命令字符串,否则当前 Shell 会优先解释这些符号,导致逻辑错误。
# 推荐写法:使用双引号包裹整个命令串parallel "command {} | chain_cmd > output_{}"管道与重定向:解压文件 -> 搜索内容 -> 保存到独立文件
错误写法:
ls *.gz | parallel zcat {} | grep "Error" > {}.log当前 Shell 会先启动 grep "Error",然后把 parallel 的所有输出都喂给它,grep 是串行处理的。
正确写法:
ls *.gz | parallel "zcat {} | grep 'Error' > {.}.log"命令序列:先创建目录,再移动文件
错误写法:
ls *.txt | parallel mkdir -p tmp; mv {} tmp/正确写法:
ls *.txt | parallel "mkdir -p tmp; mv {} tmp/"调用 Bash 函数
Section titled “调用 Bash 函数”当需要执行多步操作或包含逻辑判断时,将逻辑封装进函数,导出函数,然后让 Parallel 调用:
批量生成缩略图(跳过已存在的)
make_thumbnail() { local img="$1" local filename=$(basename "$img") local base="${filename%.*}" local thumb="thumbnails/${base}_thumb.jpg"
echo "Processing $img..."
if [ -f "$thumb" ]; then echo "Skip $img (already has thumbnail)" return fi
mkdir -p thumbnails convert -resize 200x "$img" "$thumb" echo "Done: $thumb"}
export -f make_thumbnail
parallel make_thumbnail ::: *.jpgman parallel- GNU Parallel 官方网站
- GNU Parallel 教程