安装

1
brew install parallel

官方文档指南

1
man parallel_tutorial

输入源

直接在命令行传递参数

1
::: 参数1 ::: 参数2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ echo ::: a b c e
b
a
c
e
$ parallel echo ::: a b c e ::: f g j i
a g
a j
a i
b f
b j
b i
b g
c f
c g
c j
c i
e f
e g
a f
e j
e i

以文件作为输入

1
2
3
4
5
-a file1 -a file2
:::: file1 :::: file2
1
2
3
parallel -a abc-file echo
parallel -a a.file -a b.file echo

这样子就可以并行输出文件 abc-file 的内容(默认情况下, 是无序的, 如果想有序, 则传参数 -k 即可)

以标准输输入作为输入 stdin

1
-

直接 - 后不接其他东西即可.

例如

1
2
3
4
5
6
$ seq 5 | parallel echo -
2
4
3
1
5

混合

{% raw %}:::{% endraw %} 可以和 {% raw %}::::{% endraw %} 混合使用.

跳过空行

1
parallel --no-run-if-empty

指定输入结束符

1
2
3
$ parallel -E stop echo ::: A B stop C D
A
B

修改参数界定符

使用 NUL 作为办分割符

1
2
3
--null
-0

修改文件界定符

默认情况下 {% raw %}::::{% endraw %} 作为文件的分界符, 可以使用

1
--arg-file-sep sep-str

来修改

修改参数界定符

默认情况下, {% raw %}:::{% endraw %} 作为参数分界符, 可以使用

1
--arg-sep sep-str

来修改

显示进度

1
2
3
4
5
6
7
8
9
10
11
12
--bar
--eta
--progress
输出到文件
--joblog
parallel --joblog /tmp/log
1
seq 1000 | parallel -j30 --bar '(echo {};sleep 0.1)'

使用指定分割符

1
-d delim

指定输入项是以 delim 来结束的.

替换符

{% raw %}{}{% endraw %}

默认情况下, {% raw %}{}{% endraw %} 代表输入项.

可以通过 -I 来修改.

{% raw %}{.}{% endraw %}

将输入项去掉扩展名后输出.

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ parallel --no-run-if-empty --bar -a test.file echo {} {.}
0% 0:4=0s sub.dir/bar
sub.dir/bar sub.dir/bar
25% 1:3=0s sub.dir/bar
sub.dir/foo.jpg sub.dir/foo
50% 2:2=0s sub.dir/bar
subdir/foo.jpg subdir/foo
75% 3:1=0s sub.dir/bar
foo.jpg foo
100% 4:0=0s sub.dir/bar

{% raw %}{/}{% endraw %}

获取输入项的文件名

{% raw %}{/.}{% endraw %}

获取输入项的文件名, 但去掉扩展名

{% raw %}{//}{% endraw %}

获取输入项的目录名

`{% raw %}

{% endraw %}`

任务的ID

{% raw %}{%}{% endraw %}

任务的 slot 数字

1
2
3
4
5
$ parallel --no-run-if-empty -a test.file echo {#} {%} {.}
2 2 subdir/foo
1 1 foo
3 3 sub.dir/foo
4 4 sub.dir/bar

{% raw %}{n}{% endraw %}

输入源的第 n 个参数.

1
2
3
4
$ parallel echo {2} ::: a b c ::: "这是第二个参数" ::: "这是第三个参数"
这是第二个参数
这是第二个参数
这是第二个参数

{% raw %}{n.}{% endraw %}

{% raw %}{n}{% endraw %} , 只是去掉了文件扩展名

{% raw %}{n/}{% endraw %}

{% raw %}{/}{% endraw %} , 只是处理第 n 个参数的情况

{% raw %}{n//}{% endraw %}

{% raw %}{//}{% endraw %}, 只是处理第 n 个参数的情况

{% raw %}{n/.}{% endraw %}

{% raw %}{/}{% endraw %} , 只是处理第 n 个参数的情况, 并且去掉扩展名.

限制参数长度和大小

--xargs

可以让 parallel 支持多个参数

有无 xargs 的区别

1
2
3
4
5
6
$ parallel --xargs echo ::: a b c ::: "这是第二个参数" ::: "这是第三个参数"
a 这是第二个参数 这是第三个参数 b 这是第二个参数 这是第三个参数 c 这是第二个参数 这是第三个参数
$ parallel echo ::: a b c ::: "这是第二个参数" ::: "这是第三个参数"
a 这是第二个参数 这是第三个参数
b 这是第二个参数 这是第三个参数
c 这是第二个参数 这是第三个参数

--jobs 4

指定任务数

-N3

限制参数最大个数

处理空格

1
--trim <n|l|r|lr|rl> Trim white space in input.

输出打印处理

前缀

1
--tag

以参数作为输出前缀

1
2
3
4
5
6
7
8
9
10
11
$ seq 10 | parallel --tagstring "hello {} ===> " echo -
hello 2 ===> 2
hello 4 ===> 4
hello 5 ===> 5
hello 6 ===> 6
hello 8 ===> 8
hello 9 ===> 9
hello 7 ===> 7
hello 3 ===> 3
hello 1 ===> 1
hello 10 ===> 10

把输出结果保存到文件

1
parallel --files

默认保存在 /tmp 目录中.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ seq 10 | parallel --files --tagstring "hello {} ===> " echo -
hello 2 ===> /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/parZQYda.par
hello 4 ===> /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/pariqW5T.par
hello 3 ===> /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/parhkbu1.par
hello 5 ===> /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/par4ts6Z.par
hello 6 ===> /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/parK3w5V.par
hello 7 ===> /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/par6UDLa.par
hello 8 ===> /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/par_twoW.par
hello 9 ===> /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/parPlgWZ.par
hello 1 ===> /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/parjwUR0.par
hello 10 ===> /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/parZoyUn.par
$ cat /var/folders/lz/mpwxkp6s7rq62r08vt2h_j480000gn/T/parZQYda.par
2

详细保存结果到文件

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
51
52
53
54
55
56
$ seq 10 | parallel --results outdir --tagstring "hello {} ===> " echo -
hello 2 ===> 2
hello 3 ===> 3
hello 4 ===> 4
hello 5 ===> 5
hello 6 ===> 6
hello 7 ===> 7
hello 8 ===> 8
hello 9 ===> 9
hello 1 ===> 1
hello 10 ===> 10
$ tree outdir
outdir
└── 1
├── 1
│   ├── seq
│   ├── stderr
│   └── stdout
├── 10
│   ├── seq
│   ├── stderr
│   └── stdout
├── 2
│   ├── seq
│   ├── stderr
│   └── stdout
├── 3
│   ├── seq
│   ├── stderr
│   └── stdout
├── 4
│   ├── seq
│   ├── stderr
│   └── stdout
├── 5
│   ├── seq
│   ├── stderr
│   └── stdout
├── 6
│   ├── seq
│   ├── stderr
│   └── stdout
├── 7
│   ├── seq
│   ├── stderr
│   └── stdout
├── 8
│   ├── seq
│   ├── stderr
│   └── stdout
└── 9
├── seq
├── stderr
└── stdout
11 directories, 30 files

控制并发数

1
2
3
4
5
6
7
8
9
10
--jobs N
-j N
--max-procs N
-P N

如果是0, 则意味着尽可能多的并发.(每个任务在每个CPU上尽可能 100% 占用CPU)

只打印输出命令, 则不执行

1
parallel --dryrun

交互式执行

1
parallel --interactive

恢复任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ parallel --joblog /tmp/log exit ::: 1 2 3 0
$ cat /tmp/log
Seq Host Starttime JobRuntime Send Receive Exitval Signal Command
2 : 1514099245.436 0.013 0 0 2 0 exit 2
1 : 1514099245.432 0.019 0 0 1 0 exit 1
3 : 1514099245.442 0.012 0 0 3 0 exit 3
4 : 1514099245.448 0.011 0 0 0 0 exit 0
$ parallel --resume --joblog /tmp/log exit ::: 1 2 3 0
$ cat /tmp/log
Seq Host Starttime JobRuntime Send Receive Exitval Signal Command
2 : 1514099245.436 0.013 0 0 2 0 exit 2
1 : 1514099245.432 0.019 0 0 1 0 exit 1
3 : 1514099245.442 0.012 0 0 3 0 exit 3
4 : 1514099245.448 0.011 0 0 0 0 exit 0

可以看到, 如果有执行过的话, --resum 则不会重新执行了. 只会执行其他还没有完成的

只重新执行失败的任务

1
parallel --resume-failed --joblog /tmp/log exit ::: 1 2 3 0 0 0

限制资源占用

1
2
3
4
5
6
7
8
parallel --load 100%
parallel --noswap
parallel --memfree 1G
这表示只有内存空闲大于1G时才执行任务
parallel --nice 17

使用远程主机来执行

如果远程主机使用的是非22端口, 则中以在 ~/.ssh/config 中配置:

1
2
3
4
5
6
Host host1.v
Port 22001
Host host2.v
Port 22002
Host host3.v
Port 22003

注意, 也要在远程主机上安装 parallel 才行.

1
parallel -S userName@host1.v -S userName@host2.v echo ::: hello

例子: 将当前目录下的文件传输到远程host然后压缩(虽然无实际意义, 这只是演示用法..)

1
ls | parallel -S userName@host1.v --transfer gzip {}

设置工作目录

1
ls | parallel --sshlogin userName@host1.v --workdir /tmp/new_dir/ --transferfile {} --return {}.gz --cleanup gzip {}

这将会在远程主机 hot1.v/tmp/new_dir/ 上进行文件传输.

--cleanup 表示清理远程上的相应数据或文件. --return {}.gz 表示返回相应的数据或文件. --transferfile {} 表示要传输的数据或文件.

例子收集

命令在一个文件中

假设你将一个文件保存了所有要执行的命令. 例如 每行一条 curl xxxx 之类的命令.则可以这样子:

1
cat /tmp/ad-monitor-2018-01-30_11_51_36.sh | parallel --no-run-if-empty --progress --joblog /tmp/ad-monitor-progress.log --jobs 4 > /tmp/parallel-monitor.log

注意, 不能像以下这样子

1
cat /tmp/ad-monitor-2018-01-30_11_51_36.sh | parallel --no-run-if-empty --progress --joblog /tmp/ad-monitor-progress.log --jobs 4 {} > /tmp/parallel-monitor.log

如果在后面加 {} 的话, 则会有问题. 它会转义输出我们的命令(可以用 --dryrun 选项查看要执行的实际情况, 它只是输出要执行的命令, 而不会真正执行)

批量修改文件名后缀

将所有 bz2 后缀的文件, 变成 gz 后缀

1
2
3
4
5
# 这里是尝试看看要执行的命令是什么样子的
ls *.bz2 | parallel --dryrun mv {} {/.}.gz
#真正执行
ls *.bz2 | parallel mv {} {/.}.gz

后台并行执行命令列表

1
nohup parallel --no-run-if-empty --progress --joblog /tmp/ad-monitor-progress.log --jobs 2 :::: /tmp/test-parallel.sh > /tmp/parallel-monitor.log 2>&1 &

其中 /tmp/test-parallel.sh 的内容格式如下

1
2
3
4
sleep 10
curl http://www.qq.com
sleep 10
ecoh "hello"

这样子, 就可以并行执行这些命令了. 并且是在后台进程中执行.