Как мне закодировать произвольно длинную цепочку труб?

Я немного новичок в среде Linux. Я все рассмотрел для ответа на это - извинения, если это было задано раньше.

Я написал awk script, который работает с большим текстовым файлом (11 концертов, 40 столбцов, 48M строк). script называется "cycle.awk". Он заменяет столбец новой версией. Это требует, чтобы данные сначала сортировались в этом конкретном столбце. Чтобы запустить script во всех столбцах, я написал команду bash следующим образом:

cat input.csv |
    sort -k 22 -t "," | awk -v val=22 -f cycle.awk |
    sort -k 23 -t "," | awk -v val=23 -f cycle.awk |
    sort -k 24 -t "," | awk -v val=24 -f cycle.awk |
    sort -k 25 -t "," | awk -v val=25 -f cycle.awk |
    sort -k 26 -t "," | awk -v val=26 -f cycle.awk |
    sort -k 27 -t "," | awk -v val=27 -f cycle.awk |
    sort -k 28 -t "," | awk -v val=28 -f cycle.awk |
    sort -k 29 -t "," | awk -v val=29 -f cycle.awk |
    sort -k 30 -t "," | awk -v val=30 -f cycle.awk |
    sort -k 31 -t "," | awk -v val=31 -f cycle.awk |
    sort -k 32 -t "," | awk -v val=32 -f cycle.awk |
    sort -k 33 -t "," | awk -v val=33 -f cycle.awk |
    sort -k 34 -t "," | awk -v val=34 -f cycle.awk |
    sort -k 35 -t "," | awk -v val=35 -f cycle.awk |
    sort -k 36 -t "," | awk -v val=36 -f cycle.awk |
    sort -k 37 -t "," | awk -v val=37 -f cycle.awk |
    sort -k 38 -t "," | awk -v val=38 -f cycle.awk |
    sort -k 39 -t "," | awk -v val=39 -f cycle.awk |
    sort -k 40 -t "," | awk -v val=40 -f cycle.awk |
    sort -k 41 -t "," | awk -v val=41 -f cycle.awk > output.csv

Я считаю, что должен быть более элегантный способ сделать это. Как я могу написать bash script, который позволит мне передать столбцы, которые я хочу применить к awk script, а затем выполнить такую ​​процедуру трубопровода без необходимости создавать временные файлы данных? Я избегаю временных файлов, потому что входной файл настолько велик, и меня интересует оптимальная производительность.

BTW, script выглядит следующим образом. Это в основном сокращает значения некоторых столбцов для сжатия текстового файла. Любые указатели на то, как его затянуть? Эти процедуры занимают около 10 часов.

BEGIN{ FS=","; OFS=","; count=1 }
NR == 1 { temp=$val }
{
    if ( temp != $val ) {
        temp=$val;
        count++;
    }
    $val=count
    print $0
}

Вход обычно выглядит примерно так:

id,c1
1,abcd
2,efgh
3,abcd
4,abcd
5,efgh

где соответствующий выход будет:

id,c1
1,1
2,2
3,1
4,1
5,2

Технически, он будет отсортирован с помощью c1, но это не точка.

Ответ 1

Настоящий правильный ответ - переписать ваш процесс, чтобы он не нуждался в таком конвейере. Однако, если вы хотите настроить такой конвейер, используйте рекурсивную функцию (которая соединяет себя):

process_column() {
  sort -k "$1" -t, | awk -v val="$1" -f cycle.awk
}

process_column_range() {
  local min_col=$1
  local max_col=$2
  if (( min_col < max_col )); then
    process_column "$min_col" \
     | process_column_range "$(( min_col + 1 ))" "$max_col"
  else
    process_column "$min_col"
  fi
}

... и затем, чтобы вызвать (обратите внимание, что не требуется cat):

process_column_range 22 41 <input.csv >output.csv

Ответ 2

Здесь используется двухпроходный подход с использованием алгоритма, предложенного @robmayoff. Он может быть реализован в awk или native bash (последний при значительном снижении производительности), но я собираюсь использовать Python для чтения:

#!/usr/bin/env python

import sys, collections, itertools

input_file_name = sys.argv[1]
col_start = int(sys.argv[2])
col_end = int(sys.argv[3]) + 1

vals = collections.defaultdict(set)

# first pass: build up translation tables for each column
for line in open(input_file_name, 'r'):
  cols = line.split(',') # if this is real CSV, use the Python csv module instead
  for col_no in range(col_start, col_end):
    val = cols[col_no]
    if not val in vals[col_no]: # O(1) operation on sets, vs O(n) on lists
      vals[col_no].add(val)

# interim processing: make sets into dicts w/ values in ascending order
for col_no in vals.iterkeys():
  vals[col_no] = dict(itertools.izip(sorted(list(vals[col_no])),
                                     (str(n) for n in itertools.count())))

# second pass: apply translation tables and print output
for line in open(input_file_name, 'r'):
  cols = line.split(',')
  for col_no in range(col_start, col_end):
    val = cols[col_no]
    cols[col_no] = vals[col_no][val]
  print ','.join(cols)

Я не предлагаю это как принятый ответ, так как он фактически не отвечает на поставленный вопрос (построение цепей труб), но если количество уникальных значений в перенумерованных столбцах невелико, это может быть полезно вам.

Вызывать как:

./process_column_range input.csv 22 41 >output.csv

Ответ 3

Вот мое предложение для двухпроходного решения, основанного на комментариях @robmayoff. Он использует gawk (который предлагает встроенные функции сортировки). Пока пространство, необходимое для хранения всех значений столбца, не попадает в диапазон с несколькими гигабайтами, оно должно работать хорошо и будет намного быстрее, чем выполнение 20 видов и awk-проходов.

Этот пример сортируется по столбцам 2, 3 и 4.

s.awk:

# makemaps() replaces the values in the str-indexed arrays with integers,
# sorted by ascending index value
function makemaps() {
    PROCINFO["sorted_in"]="@ind_str_asc";
    n=1;
    for(i in A2) A2[i]=n++;
    n=1;
    for(i in A3) A3[i]=n++;
    n=1;
    for(i in A4) A4[i]=n++;
    mapsdone=1;
}
BEGIN { FS=","; OFS=","; mapsdone=0; }
{
    if (NR == FNR) {
        # first pass
        # allocate array elements by index. Don't need to assign values yet.
        A2[$2];A3[$3];A4[$4];
    } else {
        # second pass
        # if not yet done, set up arrays' values to be small sequential integers
        if (!mapsdone) makemaps();
        # replace fields with the corresponding small integers
        $2=A2[$2];
        $3=A3[$3];
        $4=A4[$4];
        print $0;
    }
}

входной файл:

1,abcd,red,mercury
2,efgh,orange,mercury
3,abcd,green,venus
4,abcd,blue,earth
5,efgh,red,earth

вывода gawk -f s.awk input input (вам нужно дважды указать входной файл):

1,1,4,2
2,2,3,2
3,1,2,3
4,1,1,1
5,2,4,1

В качестве более крупного теста я использовал этот script для создания 48-миллионного входного файла с тремя столбцами с 12 символами:

BEGIN {
    OFS=",";
    for(i=0; i<48000000; i++) {
        print i,
        "aaaaaaaaa" int(1000*rand()),
        "bbbbbbbbb" int(1000*rand()),
        "ccccccccc" int(1000*rand());
    }
}

Запуск /usr/bin/time -v awk -f s.awk input input > output привел к

Присвоение команды: "awk -f s.awk input input"
Время пользователя (в секундах): 139,73
Системное время (в секундах): 6,12
Процент ЦП получен в этой задаче: 94%
Время простоя (настенные часы) (h: мм: ss или m: ss): 2: 34,85
Максимальный размер резидентного набора (кбайты): 1896

Это на одноядерном CPU VMWare на частоте 3.4 ГГц.

Итак, с 20 столбцами это может занять около 17 минут и использовать не более 15 мегабайт оперативной памяти.