Как мне закодировать произвольно длинную цепочку труб?
Я немного новичок в среде 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 мегабайт оперативной памяти.