GNU Parallel

来源:互联网 发布:普通java项目编译运行 编辑:程序博客网 时间:2024/06/06 05:34
  • GNU Parallel
  • 它是什么?
  • 指南
  • 预备
  • parallel >= version 20130814
  • abc-file
  • def-file
  • abc0-file
  • abc_-file
  • tsv_file.tsv
  • num30000
  • num1000000
  • num_%header
  • 远程执行:ssh免密码登录$SERVER1和$SERVER2
  • 输入源
  • 单个输入源
  • 多输入源
  • 适配参数
  • 改变参数分隔符
  • 改变参数定界符
  • 输入源中的结束值
  • 跳过空行
  • 构建命令行
  • 没有指定命令意味着参数就是命令
  • 替换字符串
  • 5种替换字符串
  • 改变替换字符串
  • 指定位置替换字符串
  • 按列输入
  • 指定参数名
  • 多参数
  • 引用
  • 去除空格
  • 控制输出
  • 把输出保存到文件中
  • 控制执行
  • 并行任务数
  • 交互
  • GNU Parallel

    它是什么?

    GNU Parallel是一个shell工具,为了在一台或多台计算机上并行的执行计算任务,一个计算任务可以是一条shell命令或者一个以每一行做为输入的脚本程序。通常的输入是文件列表、主机列表、用户列表、URL列表或者表格列表;一个计算任务也可以是一个从管道读取的一条命令。GNU Parallel会把输入分块,然后通过管道并行的执行。

    如果你会使用xargs和tee命令,你会发现GNU Parallel非常易于使用,因为GNU Parallel具有与xargs一样的选项。GNU Parallel可以替代大部分的shell循环,并且用并行的方式更快的完成计算任务。

    GNU Parallel保证它的输出与顺序执行计算任务时是一样的,这样就可以方便的把GNU Parallel的输出做为其它程序的输入。

    对于每一行输入,GNU Parallel会把这一行做为参数来运行指定的命令。如果没有给出命令,那么这一行会被当做命令执行。多行输入会并行的运行。GNU Parallel经常被用于替代xargs或者cat | bash。

    指南

    本教程展示了绝大多数GNU Parallel的功能。旨在介绍GNU Parallel中的一个选项,而非讲解真实世界中使用的例子。花一个小时的时间学习本教程,你会由此爱上上命令行。

    预备

    为了执行本教程中的示例,你首先需要做如下准备:

    parallel >= version 20130814

    安装最新版:

    1(wget -O - pi.dk/3 || curl pi.dk/3/) | bash

    这条命令同时也会安装最新版的指南

    1man parallel_tutorial

    本教程的大部分内容同时也兼容旧版本。

    abc-file

    生成文件:

    1parallel -k echo ::: A B C > abc-file

    def-file

    生成文件:

    1parallel -k echo ::: D E F > def-file

    abc0-file

    生成文件:

    1perl -e 'printf "A\0B\0C\0"' > abc0-file

    abc_-file

    生成文件:

    1perl -e 'printf "A_B_C_"' > abc_-file

    tsv_file.tsv

    生成文件:

    1perl -e 'printf "f1\tf2\nA\tB\nC\tD\n"' > tsv-file.tsv

    num30000

    生成文件:

    1perl -e 'for(1..30000){print "$_\n"}' > num30000

    num1000000

    生成文件:

    1perl -e 'for(1..1000000){print "$_\n"}' > num1000000

    num_%header

    生成文件:

    1(echo %head1; echo %head2; perl -e 'for(1..10){print "$_\n"}') > num_%header

    远程执行:ssh免密码登录$SERVER1和$SERVER2

    生成文件:

    1SERVER1=server.example.com
    2SERVER2=server2.example.net

    最后应该成功运行如下命令:

    1ssh $SERVER1 echo works
    2ssh $SERVER2 echo works

    使用 ssh-keygen -t dsa; ssh-copy-id $SERVER1 建立环境(使用empty pass phrase)

    输入源

    GNU Parallel的输入源支持文件、命令行和标准输入(stdin或pipe)

    单个输入源

    从命令行读取输入:

    1parallel echo ::: A B C

    输出(由于任务以并行的方式执行,顺序可能会有所不同):

    1A
    2B
    3C

    文件做为输入源:

    1parallel -a abc-file echo

    输出同上。

    STDIN(标准输入)做为输入源:

    1cat abc-file | parallel echo

    输出同上。

    多输入源

    GNU Parallel支持通过命令行指定多个输入源,它会生成所有的组合:

    1parallel echo ::: A B C ::: D E F

    输出:

    1A D
    2A E
    3A F
    4B D
    5B E
    6B F
    7C D
    8C E
    9C F

    多个文件做为输入源:

    1parallel -a abc-file -a def-file echo

    输出同上。

    STDIN(标准输入)可以做为输入源中的一个,使用“-”:

    1cat abc-file | parallel -a - -a def-file echo

    输出同上。

    可以使用“::::”替代 -a:

    1cat abc-file | parallel echo :::: - def-file

    输出同上。

    ::: 和 :::: 可以混合使用:

    1parallel echo ::: A B C :::: def-file

    输出同上。

    适配参数

    –xapply 从每一个输入源取一个参数:

    1parallel --xapply echo ::: A B C ::: D E F

    输出:

    1A D
    2B E
    3C F

    如果其中一个输入源的长度比较短,它的值会被重复:

    1parallel --xapply echo ::: A B C D E ::: F G

    输出:

    1A F
    2B G
    3C F
    4D G
    5E F

    改变参数分隔符

    GNU Parallel可以指定分隔符替代 ::: 或 ::::,当这两个符号被其它命令占用的时候会特别有用:

    1parallel --arg-sep ,, echo ,, A B C :::: def-file

    输出:

    1A D
    2A E
    3A F
    4B D
    5B E
    6B F
    7C D
    8C E
    9C F

    改变参数分隔符:

    1parallel --arg-file-sep // echo ::: A B C // def-file

    输出同上。

    改变参数定界符

    GNU Parallel默认把一行做为一个参数:使用 \n 做为参数定界符。可以使用 -d 改变:

    1parallel -d _ echo :::: abc_-file

    输出:

    1A
    2B
    3C

    \0 代表NULL:

    1parallel -d '\0' echo :::: abc0-file

    输出同上。 
    -0 是 -d '\0' 的简写(通常用于从 find … -print0读取输入):

    1parallel -0 echo :::: abc0-file

    输出同上。

    输入源中的结束值

    GNU Parallel支持指定一个值做为结束标志:

    1parallel -E stop echo ::: A B stop C D

    输出:

    1A
    2B

    跳过空行

    使用 –no-run-if-empty 来跳过空行:

    1(echo 1; echoecho 2) | parallel --no-run-if-empty echo

    输出:

    11
    22

    构建命令行

    没有指定命令意味着参数就是命令

    如果parallel之后没有给定命令,那么这些参数会被当做命令:

    1parallel ::: ls 'echo foo' pwd

    输出:

    1[当前文件列表]
    2foo
    3[当前工作目录的路径]

    命令可以是一个脚本文件,一个二进制可执行文件或一个bash的函数(须用 export -f 导出函数):

    1# Only works in Bash and only if $SHELL=.../bash
    2my_func() {
    3  echo in my_func $1
    4}
    5export -f my_func
    6parallel my_func ::: 1 2 3

    输出:

    1in my_func 1
    2in my_func 2
    3in my_func 3

    替换字符串

    5种替换字符串

    GNU Parallel支持多种替换字符串。默认使用 {}:

    1parallel echo ::: A/B.C

    输出:

    1A/B.C

    指定 {} :

    1parallel echo {} ::: A/B.C

    输出同上 
    去掉扩展名 {.}:

    1parallel echo {.} ::: A/B.C

    输出

    1A/B

    去掉路径 {/}:

    1parallel echo {/} ::: A/B.C

    输出:

    1B.C

    只保留路径 {//}:

    1parallel echo {//} ::: A/B.C

    输出:

    1A

    去掉路径和扩展名 {/.}:

    1parallel echo {/.} ::: A/B.C

    输出:

    1B

    输出任务编号:

    1parallel echo {#} ::: A/B.C

    输出:

    11
    22
    33

    改变替换字符串

    使用 -I 改变替换字符串符号 {}:

    1parallel -I ,, echo ,, ::: A/B.C

    输出:

    1A/B.C

    –extensionreplace替换 {.}:

    1parallel --extensionreplace ,, echo ,, ::: A/B.C

    输出:

    1A/B

    –basenamereplace替换 {/}:

    1parallel --basenamereplace ,, echo ,, ::: A/B.C

    输出:

    1B.C

    –dirnamereplace替换 {//}:

    1parallel --dirnamereplace ,, echo ,, ::: A/B.C

    输出:

    1A

    –basenameextensionreplace替换 {/.}:

    1parallel --basenameextensionreplace ,, echo ,, ::: A/B.C

    输出:

    1B

    –seqreplace替换 {#}:

    1parallel --seqreplace ,, echo ,, ::: A B C

    输出:

    11
    22
    33

    指定位置替换字符串

    如果有多个输入源时,可以通过 {编号} 指定某一个输入源的参数:

    1parallel echo {1} and {2} ::: A B ::: C D

    输出:

    1A and C
    2A and D
    3B and C
    4B and D

    可以使用 / // /. 和 .: 改变指定替换字符串:

    1parallel echo /={1/} //={1//} /.={1/.} .={1.} ::: A/B.C D/E.F

    输出:

    1/=B.C //=A /.=B .=A/B
    2/=E.F //=D /.=E .=D/E

    位置可以是负数,表示倒着数:

    1parallel echo 1={1} 2={2} 3={3} -1={-1} -2={-2} -3={-3} ::: A B ::: C D ::: E F

    输出:

    11=A 2=C 3=E -1=E -2=C -3=A
    21=A 2=C 3=F -1=F -2=C -3=A
    31=A 2=D 3=E -1=E -2=D -3=A
    41=A 2=D 3=F -1=F -2=D -3=A
    51=B 2=C 3=E -1=E -2=C -3=B
    61=B 2=C 3=F -1=F -2=C -3=B
    71=B 2=D 3=E -1=E -2=D -3=B
    81=B 2=D 3=F -1=F -2=D -3=B

    按列输入

    使用 –colsep 把文件中的行切分为列,做为输入参数。下面使用TAB(\t):

    11=f1 2=f2
    21=A 2=B
    31=C 2=D

    指定参数名

    使用 –header 把每一行输入中的第一个值做为参数名:

    1parallel --header : echo f1={f1} f2={f2} ::: f1 A B ::: f2 C D

    输出:

    1f1=A f2=C
    2f1=A f2=D
    3f1=B f2=C
    4f1=B f2=D

    使用 –colsep 处理使用TAB做为分隔符的文件:

    1parallel --header : --colsep '\t' echo f1={f1} f2={f2} :::: tsv-file.tsv

    输出:

    1f1=A f2=B
    2f1=C f2=D

    多参数

    –xargs 让GNU Parallel支持一行多个参数(可以指定上限):

    1cat num30000 | parallel --xargs echo wc -l

    输出:

    12

    30000个参数被分为两行。 
    一行中的参数个数的上限通过 -s 指定。下面指定最大长度是10000,会被分为17行:

    1cat num30000 | parallel --xargs -s 10000 echo wc -l

    输出:

    1 

    为了获得更好的并发性,GNU Parallel会在文件读取结束后再分发参数。

    GNU Parallel 在读取完最后一个参数之后,才开始第二个任务,此时会把所有的参数平均分配到4个任务(如果指定了4个任务)。

    第一个任务与上面使用 –xargs 的例子一样,但是第二个任务会被平均的分成4个任务,最终一共5个任务。

    1cat num30000 | parallel --jobs 4 -m echo wc -l

    输出:

    15

    10分参数分配到4个任务可以看得更清晰:

    1parallel --jobs 4 -m echo ::: {1..10}

    输出:

    11 2 3
    24 5 6
    37 8 9
    410

    替换字符串可以是单词的一部分。通过下面两个命令体会 -m 和 -X 的区别:

    1parallel --jobs 4 -m echo pre-{}-post ::: A B C D E F G

    输出:

    1pre-A B-post
    2pre-C D-post
    3pre-E F-post
    4pre-G-post

    -X与 -m 相反:

    1parallel --jobs 4 -X echo pre-{}-post ::: A B C D E F G

    输出:

    1pre-A-post pre-B-post
    2pre-C-post pre-D-post
    3pre-E-post pre-F-post
    4pre-G-post

    使用 -N 限制每行参数的个数:

    1parallel -N3 echo ::: A B C D E F G H

    输出:

    1A B C
    2D E F
    3G H

    -N也可以用于指定位置替换字符串:

    1parallel -N3 echo 1={1} 2={2} 3={3} ::: A B C D E F G H

    输出:

    11=A 2=B 3=C
    21=D 2=E 3=F
    31=G 2=H 3=

    -N0 只读取一个参数,但不附加:

    1parallel -N0 echo foo ::: 1 2 3

    输出:

    1foo
    2foo
    3foo

    引用

    如果命令行中包含特殊字符,就需要使用引号保护起来。

    perl脚本 'print “@ARGV\n”' 与linux的 echo 的功能一样。

    1perl -e 'print "@ARGV\n"' A

    输出:

    1A

    使用GNU Parallel运行这条命令的时候,perl命令需要用引号包起来:

    1parallel perl -e 'print "@ARGV\n"' ::: This wont work

    输出:

    1[Nothing]

    使用 -q 保护perl命令:

    1parallel -q perl -e 'print "@ARGV\n"' ::: This works

    输出:

    1This
    2works

    也可以使用 ' :

    1parallel perl -e \''print "@ARGV\n"'\' ::: This works, too

    输出:

    1This
    2works,
    3too

    使用 -quote:

    1parallel --shellquote
    2parallel: Warning: Input is read from the terminal. Only experts do this on purpose. Press CTRL-D to exit.
    3perl -e 'print "@ARGV\n"'
    4[CTRL-D]

    输出:

    1perl\ -e\ \'print\ \"@ARGV\\n\"\'

    也可以使用命令:

    1parallel perl\ -e\ \'print\ \"@ARGV\\n\"\' ::: This also works

    输出:

    1This
    2also
    3works

    去除空格

    使用 –trim 去除参数两头的空格:

    1parallel --trim r echo pre-{}-post ::: ' A '

    输出:

    1pre- A-post

    删除左边的空格:

    1parallel --trim l echo pre-{}-post ::: ' A '

    输出:

    1pre-A -post

    删除两边的空格:

    1parallel --trim lr echo pre-{}-post ::: ' A '

    输出:

    1pre-A-post

    控制输出

    以参数做为输出前缀:

    1parallel --tag echo foo-{} ::: A B C

    输出:

    1A       foo-A
    2B       foo-B
    3C       foo-C

    修改输出前缀 –tagstring:

    1parallel --tagstring {}-bar echo foo-{} ::: A B C

    输出:

    1A-bar       foo-A
    2B-bar       foo-B
    3C-bar       foo-C

    查看有哪些命令会被执行:

    1parallel --dryrun echo {} ::: A B C

    输出:

    1echo A
    2echo B
    3echo C

    运行之前先打印命令 –verbose:

    1parallel --verbose echo {} ::: A B C

    输出:

    1echo A
    2echo B
    3A
    4echo C
    5B
    6C

    GNU Parallel 会延迟输出,直到命令执行完成:

    1parallel -j2 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1

    输出:

    12-start
    22-middle
    32-end
    41-start
    51-middle
    61-end
    74-start
    84-middle
    94-end

    立即打印输出 –ungroup:

    1parallel -j2 --ungroup 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1

    输出:

    14-start
    242-start
    32-middle
    42-end
    51-start
    61-middle
    71-end
    8-middle
    94-end

    使用 –ungroup 会很快,但会导致输出错乱,一个任务的行输出可能会被另一个任务的输出截断。像上例所示,第二行输出混合了两个任务: '4-middle' '2-start'

    使用 –linebuffer避免这个问题(稍慢一点):

    1parallel -j2 --linebuffer 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1

    输出:

    14-start
    22-start
    32-middle
    42-end
    51-start
    61-middle
    71-end
    84-middle
    94-end

    强制使输出与参数保持顺序 –keep-order/-k:

    1parallel -j2 -k 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1

    输出:

    14-start
    24-middle
    34-end
    42-start
    52-middle
    62-end
    71-start
    81-middle
    91-end

    把输出保存到文件中

    GNU Parallel可以把每一个任务的输出保存到文件中:

    1parallel --files ::: A B C

    输出与下面类似:

    1/tmp/pAh6uWuQCg.par
    2/tmp/opjhZCzAX4.par
    3/tmp/W0AT_Rph2o.par

    临时文件默认保存在 /tmp 中,可以使用 –tmpdir改变(或者修改 $TMPDIR):

    1parallel --tmpdir /var/tmp --files ::: A B C

    输出:

    1/var/tmp/pAh6uWuQCg.par
    2/var/tmp/opjhZCzAX4.par
    3/var/tmp/W0AT_Rph2o.par

    或者修改 $TMPDIR :

    1TMPDIR=/var/tmp parallel --files ::: A B C

    输出同上。

    输出文件可以有结构的保存 –results:

    1parallel --results outdir echo ::: A B C

    输出:

    1A
    2B
    3C

    输出文件不仅包含标准输出(stdout)也会包含标准错误输出(stderr):

    1outdir/1/A/stderr
    2outdir/1/A/stdout
    3outdir/1/B/stderr
    4outdir/1/B/stdout
    5outdir/1/C/stderr
    6outdir/1/C/stdout

    在使用多个变量的时候会显示很有用:

    1parallel --header : --results outdir echo ::: f1 A B ::: f2 C D

    生成的文件:

    1outdir/f1/A/f2/C/stderr
    2outdir/f1/A/f2/C/stdout
    3outdir/f1/A/f2/D/stderr
    4outdir/f1/A/f2/D/stdout
    5outdir/f1/B/f2/C/stderr
    6outdir/f1/B/f2/C/stdout
    7outdir/f1/B/f2/D/stderr
    8outdir/f1/B/f2/D/stdout

    控制执行

    并行任务数

    使用 –jobs/-j 指定并行任务数:

    1/usr/bin/time parallel -N0 -j64 sleep 1 ::: {1..128}

    使用64个任务执行128个休眠命令,大概耗时2到8秒。

    默认情况下并行任务数与cpu核心数相同,所以这条命令:

    1/usr/bin/time parallel -N0 sleep 1 ::: {1..128}

    会比每个cpu两个任务的耗时多一倍:

    1/usr/bin/time parallel -N0 --jobs 200% sleep 1 ::: {1..128}

    使用 –jobs 0 表示执行尽可能多的并行任务:

    1/usr/bin/time parallel -N0 --jobs 0 sleep 1 ::: {1..128}

    通常耗时1到7秒。

    可以从文件中读取并行任务数,这样的话,每个任务完成的时候都会重新读取一次文件:

    1echo 50% > my_jobs
    2/usr/bin/time parallel -N0 --jobs my_jobs sleep 1 ::: {1..128} &
    3sleep 1
    4echo 0 > my_jobs
    5wait

    前两个任务都是只用了一半的cpu,当文件内容变成0之后,后面的任务就会尽可能多的并行执行。

    除了基于cpu使用率之外,也可以基于cpu数:

    1parallel --use-cpus-instead-of-cores -N0 sleep 1 ::: {1..128}

    交互

    通过使用 –interactive 在一个任务执行之前让用户决定是否执行:

    1parallel --interactive echo ::: 1 2 3

    输出:

    1echo 1 ?...y
    2echo 2 ?...n
    31
    4echo 3 ?...y
    53

    未完,待续


    来源: <http://my.oschina.net/enyo/blog/271612>
     
    0 0
    原创粉丝点击