как делать с GNU параллельно, что эквивалентно «читать слово1 слово2» - PullRequest
0 голосов
/ 24 января 2019

У меня есть труба, которая дает мне строки из двух строк в кавычках, разделенных пробелами. Используя echo, чтобы дать вам пример содержимого канала:

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

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

Первая строка - это имя файла, а вторая - текст, который я хочу добавить в этот файл. Получить дескриптор $ filename и $ text с помощью «read» очень просто:

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"

но "параллель" не хочет обрабатывать две строки в строке как два параметра. Кажется, к ним относятся как к одному.

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 голосов
/ 28 января 2019

При параллельном выполнении заданий вы рискуете состязанием: если два задания добавляются в один и тот же файл в одно и то же время, содержимое файла может быть искажено.

Есть несколько способов избежать этого:

Отдельные рабочие каталоги

Имея отдельные рабочие каталоги, каждый процесс будет добавлять файлы только в свой рабочий каталог. Когда работа завершена, рабочие каталоги должны быть объединены.

Если входной файл равен 1 ТБ, это означает, что для запуска вам необходимо 2 ТБ.

Поместите имена файлов в контейнеры

Если все файлы с заданным именем присваиваются только одному процессу, ни один другой процесс не будет добавляться одновременно. Один из способов сделать это - вычислить хэш имени файла и распространить его среди рабочих на основе значения хеша.

Что-то похожее на:

#!/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 ТБ, это означает, что для запуска вам необходимо 1 ТБ.

Сгруппировать имена файлов

Это похоже на предыдущую идею, но вместо хэширования каждой строки вы сортируете входной файл, вставляете маркер после каждого нового имени файла и позволяете GNU Parallel использовать маркер в качестве конца записи. Чтобы это работало, вам нужно иметь несколько выходных файлов, чтобы вы могли хранить все записи нескольких файлов в памяти одновременно.

Если входной файл равен 1 ТБ, это означает, что для запуска вам необходимо 2 ТБ.

0 голосов
/ 24 января 2019

Параллельно обрабатывает кавычки / экранирование вполне корректно, поэтому не стесняйтесь вначале упростить ввод - просто разместите его по перемеженным строкам, чтобы 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 '
0 голосов
/ 24 января 2019

Это то, что я в итоге сделал, когда awk берет на себя разделение полей, а символ разделителя - "," на выходе предыдущего канала. (между прочим, параллельная скорость в 30 раз выше, чем до голого ака):

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

Но правильным ответом на мой первоначальный вопрос о параллели, вероятно, является комбинация флага --csv --colsep ' ' от @ George-P https://stackoverflow.com/a/54340352/4634344. Я пока не могу проверить это, поскольку моя параллельная версия еще не поддерживает - флаг csv.

0 голосов
/ 24 января 2019

Если вы согласны с удалением кавычек, тогда опция --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 только в последних версиях параллели (начиная с 2018-04-22).Если вы используете более старую версию parallel, вам лучше сначала преобразовать ввод с шагом предварительной обработки в параллельный формат.Единственный способ сделать это с чистым parallel - это по-настоящему хакерская эксплуатация цитирования оболочки и хакерства с parallel внутренностями:

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 покажет команду, которую он создает под капотом.

...