#gnu-parallel
#gnu-parallel
Вопрос:
Проблема
Часто мне нужно обработать каталог из нескольких CSV-файлов и создать один выходной файл. Часто я полагаюсь на GNU parallel для одновременного выполнения этих задач. Однако мне нужен способ отбросить первую строку (заголовок) для всех, кроме первого задания, которое возвращает выходные данные.
Чтобы сделать это конкретным, представьте каталог из нескольких CSV-файлов, подобных этому…
x,y
1,1.2
2,5.3
3,6.0
Затем есть какой-нибудь скрипт (Python), назовите его calc.py
, который очищает данные или выполняет вычисления…
import csv
import math
import sys
rdr = csv.DictReader(sys.stdin)
wtr = csv.DictWriter(sys.stdout, fieldnames=['x', 'y', 'siny'])
wtr.writeheader()
for row in rdr:
row['siny'] = math.sin(float(row['y']))
wtr.writerow(row)
Затем мы можем обрабатывать файлы данных параллельно с GNU parallel…
parallel --lb python calc.py '<' {} ::: $(ls -1 *.csv)
Однако это приведет к созданию нескольких строк заголовка. Например…
x,y,siny
1,1.2,0.9320390859672263
2,5.3,-0.8322674422239013
3,6.0,-0.27941549819892586
x,y,siny
4,7.2,0.7936678638491531
5,2.2,0.8084964038195901
6,0.9,0.7833269096274833
Я ищу простой способ (в идеале вариант parallel
) сохранить только первую строку заголовка в выходных данных. Материал, связанный с заголовками в руководстве, похоже, касается входных данных.
Возможные решения
Я вижу несколько вариантов, но ни один из них мне не нравится…
- Не нужно
calc.py
выводить заголовок и вместо этого повторять заголовок перед параллельным запуском. Недостатком является то, что заголовок должен быть известен, или нам нужно заглянуть в заголовок, выполнив что-то вродеpython calc.py data1.csv | head -n 1
перед запускомparallel
. - Сохраните выходные данные каждого задания в отдельный файл, затем объедините их ex post (например, с помощью
xsv
,tail
,sed
и т.д.), удалив заголовок из всех, кроме первого. Недостатком этого является необходимость управлять дополнительными файлами на диске и очищать их впоследствии. - Напишите другую программу, которая делает это, и передайте результаты
parallel
ей.- Сравнение каждой строки выходных данных с первой требует больших затрат процессора, и мы знаем, что несколько записей будут совпадать.
- Предполагается, что ни одна допустимая запись данных не соответствует строке заголовка.
Каков наилучший способ решить эту проблему? Есть ли опция, которая указывает parallel игнорировать все строки заголовка, кроме одной, в выходных данных каждого задания?
Комментарии:
1. Я также пытался найти способ сделать это с помощью GNU parallel и не смог его найти. Были оставлены типы опций, которые вы перечислили.
2. Почему бы просто не загрузить csv в виде данных на python (что вы уже делаете) и объединить, используя возможности python?
Ответ №1:
Как насчет настройки варианта 1:
Заставьте программу принимать два аргумента: file jobnumber
if jobnumber == 1:
output header
Чтобы гарантировать, что задание 1 печатается первым, используйте --keep-order
:
parallel --keep-order python calc.py {#} '<' {} ::: *.csv
GNU Parallel кэширует выходные данные выполняемых заданий в / tmp для сериализации выходных данных, которые могут быть или не быть медленнее, чем --lb
.
В общем, вы можете сделать что-то вроде:
parallel -k python 'calc.py < {} {= uq; $_ = seq()==1 ? "" : "| tail 2" =}' ::: *.csv
uq
доступно с 20190722 года.
Вы будете запускать tail
, поэтому это может быть немного медленнее. В моей системе tail
обеспечивается скорость 0,5 ГБ / с на ядро.
Комментарии:
1. Я думаю, что это лучшее решение. Было бы неплохо, если бы эту функцию (сохранение одного заголовка) можно было бы перенести в GNU parallel, однако, чтобы также решить проблему при использовании уже существующих инструментов (например,
xsv
).2. Некоторые существующие инструменты, такие как GNU datamash, уже принимают аргументы, указывающие, является ли первая строка заголовком и следует ли ее выводить. Для GNU datamash это
--header-in
и--header-out
. Если есть способ преобразовать аргумент paralleljobnumber
в аргумент командной строки существующей программы, это может быть полезно для некоторых существующих инструментов. В целом, однако, было бы определенно неплохо иметь эту функциональность (сохранение одного заголовка) внутри GNU parallel.
Ответ №2:
AFAIK, GNU Parallel не предоставляет такой опции для записи заголовка только один раз в выходных данных
Но простым подходом может быть использование awk
для вывода строки заголовка только один раз, например
parallel --lb python calc.py '<' {} ::: $(ls -1 *.csv) | awk 'NR==1 { header=$0; print $0; next; } $0 != header'
Объяснение
NR
это номер строки в awk. Когда номер строки равен 1, он сохранит его в переменной header
и напечатает только один раз. Если какая-либо строка впоследствии точно совпадет с заголовком, она будет проигнорирована
Плюсы
- Вам не нужно знать заголовок заранее
- Вам не нужно сохранять выходные данные в отдельные файлы
- Нет необходимости изменять ваш код
Минусы
- Предполагается, что ни одна строка данных не соответствует заголовку
- В случае отсутствия заголовка он будет обрезать первую строку и все одинаковые строки из выходных данных
- При сопоставлении строк возникают небольшие накладные расходы
Комментарии:
1. Спасибо за это предложение. Мне нужно будет выяснить, насколько велики накладные расходы.