如何与GNU并行处理等效于“读取word1 word2”

时间:2019-01-24 05:54:06

标签: bash gnu-parallel

我有一个管道,可以给我两行用引号引起来的空格分隔的字符串。使用echo给您一个管道内容的示例:

echo -e "\"filename1\" \"some text 1\"\n\"filename2\" \"some text 2\""

"filename1" "some text 1"
"filename2" "some text 2"

第一个字符串是文件名,第二个字符串是我要附加到该文件的文本。 通过“读取”来获取$ filename和$ text的句柄很容易:

echo -e "\"filename1\" \"some text 1\"\n\"filename2\" \"some text 2\""|
while read filename text; do echo $text $filename; done

"some text 1" "filename1"
"some text 2" "filename2"

但是“ parallel”不想将线上的两个字符串视为两个参数。似乎将它们视为一体。

echo -e "\"filename1\" \"some text 1\"\n\"filename2\" \"some text 2\""|
parallel echo {2} {1}

"filename1" "some text 1"
"filename2" "some text 2"

因此,仅在行上插入{1}即可得到相同的结果

echo -e "\"filename1\" \"some text 1\"\n\"filename2\" \"some text 2\""|
parallel echo {1}

"filename1" "some text 1"
"filename2" "some text 2"

添加--colsep ' '会破坏每个空格上的字符串

echo -e "\"filename1\" \"some text 1\"\n\"filename2\" \"some text 2\""|
parallel --colsep ' ' echo {2} {1}

"some "filename1"
"some "filename2"

我只是无法在其文档https://www.gnu.org/software/parallel/man.html

中找到有关如何通过管道进行并行处理的说明。

添加一个--delimiter ' '选项即可实现

echo -e "\"filename1\" \"some text 1\"\n\"filename2\" \"some text 2\""| 
parallel --delimiter ' ' echo {2} {1}

"filename1"
"some
text
1"
"filename2"
"some
text
2"

这是我找到的最接近的

seq 10 | parallel -N2 echo seq:\$PARALLEL_SEQ arg1:{1} arg2:{2}

seq:1 arg1:1 arg2:2
seq:2 arg1:3 arg2:4
seq:3 arg1:5 arg2:6
seq:4 arg1:7 arg2:8
seq:5 arg1:9 arg2:10

但是它并不能真正反映我的数据,因为seq 10在每个字符串之后都有一个新行,而我在该行上有两个字符串。

1
2
3
4
5
6
7
8
9
10

我当前的解决方法是将管道更改为具有逗号而不是空格来分隔行上带引号的字符串:

echo -e "\"filename1\",\"some text 1\"\n\"filename2\",\"some text 2\""|
parallel --colsep ',' echo {2} {1}

"some text 1" "filename1"
"some text 2" "filename2"

但是如何并行处理呢?

4 个答案:

答案 0 :(得分:3)

如果您对被删除的引号感到满意,那么将--csv--colsep配对的选项将会拆分到您希望的位置(并仍然保留所有空白)

echo -e "\"filename1\" \"some text 1\"\n\"filename2 withspaces\" \"some text   2\""|
parallel --csv --colsep=' ' echo arg1:{1} arg2:{2}

输出:

arg1:filename1 arg2:some text 1
arg1:filename2 withspaces arg2:some text   2

注意--csv需要安装perl Text::CSV模块(sudo cpan Text::CSV

如果您想保留引号,请混合使用-q和一些额外的引号:

echo -e "\"filename1\" \"some text 1\"\n\"filename2 withspaces\" \"some text   2\""|
parallel -q --csv --colsep=' ' echo 'arg1:"{1}" arg2:"{2}"'

输出:

arg1:"filename1" arg2:"some text 1"
arg1:"filename2 withspaces" arg2:"some text   2"

--csv仅在parallel的最新版本中(自2018-04-22开始)。如果您使用的是较旧的parallel,最好先将带有预处理步骤的输入转换为并行可以处理的格式。我能看到的使用纯parallel做到这一点的唯一方法是对parallel内部的shell引用和破坏进行真正的黑客利用:

echo -e "\"filename1\" \"some text 1\"\n\"filename2 with spaces\" \"some text    2\""|
parallel sh -c "'echo arg1:\"\$1\" arg2:\"\$2\"'" echo '{= $Global::noquote = 1 =}'

输出:

arg1:filename1 arg2:some text 1
arg1:filename2 with spaces arg2:some text    2

这是如何工作的,我将作为练习...通过parallel --shellquote运行,将显示它正在内部构造的命令。

答案 1 :(得分:1)

并行运行作业时,您可能会面临竞争条件:如果两个作业恰好同时添加到同一文件中,则文件的内容可能会出现乱码。

有几种避免这种情况的方法:

单独的工作目录

通过具有单独的工作目录,每个进程将仅追加到其自己的工作目录中的文件。工作完成后,应合并工作目录。

如果输入文件为1 TB,则意味着您需要2 TB的可用空间来运行。

将文件名放入垃圾箱

如果仅将给定名称的所有文件分配给单个进程,则不会同时添加其他进程。一种方法是计算文件名的哈希值,然后根据哈希值将其分配给工作程序。

类似的东西:

#!/usr/bin/perl

use B;

# Set the number of bins to use (typically number of cores)
$bins = 9;

for(1..$bins) {
    # Create fifo and open filehandle
    mkfifo($_);
    open $fh{$_}, ">", "fifo-$_";
}

if(not fork) {
    # Start the processors
    `parallel -j0 'cat {} | myprocess' ::: fifo-*`;
    exit;
}

my @cols;
while(<>) {
    # Get the column with the filename
    # Here we assume the columns are , separated
    @cols = split(/,/,$_);
    # We assume the value we need to group on is column 1
    # compute a hash value of the column
    # modulo number of bins
    # print output to that fifo
    print $fh{ hex(B::hash($col[1]))%$bins } $_;
}

# Cleanup
for(1..$bins) {
    close $fh{$_};
    unlink "fifo-$_";
}

如果输入文件为1 TB,则意味着您需要1 TB的可用空间来运行。

对文件名进行分组

这与先前的想法类似,但是您无需对每个行进行哈希处理,而是对输入文件进行排序,在每个新文件名后插入一个标记,然后让GNU Parallel使用该标记作为记录的结尾。为此,您需要有很多输出文件,以便可以同时在内存中存储多个文件的所有记录。

如果输入文件为1 TB,则意味着您需要2 TB的可用空间来运行。

答案 2 :(得分:0)

parallel非常正确地处理了引号/转义符,因此请随时先简化输入-只需按交织的行对其进行布局,以使parallel -n2进一步消化它:

$ echo -e '"file 1" "text 1"\n"file 2" "text 2"'
"file 1" "text 1"
"file 2" "text 2"
$ echo -e '"file 1" "text 1"\n"file 2" "text 2"'|sed 's/^"\(.*\)" "\(.*\)"/\1\n\2/'
file 1
text 1
file 2
text 2
$ echo -e "file 1\ntext 1\nfile 2\ntext 2"
file 1
text 1
file 2
text 2

运行1:

$ echo -e "file 1\ntext 1\nfile 2\ntext 2"|parallel -n2 'echo {2} >> {1}'
$ grep . file*
file 1:text 1
file 2:text 2

运行2(带引号):

$ echo -e "file 1\ntext 1 with double-quotes \"\nfile 2\ntext 2 with single-quote '"|parallel -n2 'echo {2} >> {1}'
$ grep . file*
file 1:text 1
file 1:text 1 with double-quotes "
file 2:text 2
file 2:text 2 with single-quote '

答案 3 :(得分:0)

这是我最后做的工作,其中awk接管了字段拆分,并且在前面的管道输出中,分隔符为“,”。 (顺便说一句并行可以将裸机的速度提高30倍):

parallel -j4 --pipe -q awk -F, '{ gsub("\\\\\"",""); gsub("\"",""); print($2)>>$1".txt"}'

但是,我最初关于并行的问题的正确答案可能是@ George-P https://stackoverflow.com/a/54340352/4634344--csv --colsep ' '标志组合。由于并行版本尚不支持--csv标志,因此我无法对其进行测试。