
У меня есть куча файлов, в которых мне нужно вынуть определенные строки, а затем поместить вынутые данные в электронную таблицу. Примером может служить мой файл, показывающий:
Name: w
Age: x
Height: y
Weight: z
Мне нужны только возраст, рост и вес, поэтому я сначала ввел:
grep -E 'Age|Height|Weight' [input file] > output.txt
Из-за количества файлов мой вывод теперь выглядит так:
Age 1
Height 1
Weight 1
Age 2
Height 2
Weight 2
etc...
Теперь я хочу запустить скрипт awk, чтобы он прошел по моему новому файлу output.txt и сначала нашел каждую строку со словом 'Age' и вывел ее. После того, как он сделал все строки со словом 'Age', он затем вычисляет высоту и вес. Я запустил скрипт:
awk -F"\t" '/Age/ {print} /Height/ {print}' output.txt >output2.txt
Но если просто печатает его как исходный выходной файл. Как мне изменить его так, чтобы после того, как он сделал все Возрастные, он затем нашел Ростовые?
РЕДАКТИРОВАТЬ:
Мой желаемый результат — это файл
Возраст 1
Возраст 2
Высота 1
Высота 2
Вес 1
Вес 2
и т. д..
Для ясности: Возраст 1 — это строка с надписью «возраст» из файла 1 и т. д.
решение1
По умолчанию awk проходит по файлу только один раз, запуская все блоки по порядку, поэтому он и выдает вам тот вывод, который вы получили. Вы можете получить желаемое поведение, используямассивчтобы сохранять строки по мере их поступления, при этом обрабатывая файл только один раз:
BEGIN {
AgeIndex = 1
HeightIndex = 1
}
/Age/ {
ages[AgeIndex] = $0
AgeIndex+=1
}
/Height/ {
heights[HeightIndex] = $0
HeightIndex+=1
}
END {
for (x = 1; x < AgeIndex; x++)
print ages[x] "\n"
for (x = 1; x < HeightIndex; x++)
print heights[x] "\n"
}
Сохраните это, скажем, в filter.awk
и затем запустите:
awk -f filter.awk output.txt > output2.txt
чтобы получить желаемый результат:
$ awk -f filter.awk < data
Age 1
Age 2
Height 1
Height 2
Мы делаем два массива ages
и heights
сохраняем в них каждую соответствующую строку по мере продвижения. AgeIndex
содержит, насколько далеко мы продвинулись по массиву. В конце мы выводим каждую сохраненную строку (и дополнительную новую строку, как вы хотите), сначала все возрасты, затем все высоты.
Массивы будут хранить весь файл в памяти к концу, поэтому если ваш файл особенно большой, вам придется пожертвовать этим использованием памяти ради времени, которое потребуется, чтобы пройти по всему файлу больше одного раза. На этом этапе это по сути то же самое, что и программа на любом другом языке — если у вас нет особых причин использовать awk, вы можете предпочесть другой язык. Честно говоря, я думаю, что я бы рекомендовал это — awk здесь не покупает много.
решение2
С gawk
:
$ awk -F"\t" '
{ a[$1]++ }
END {
n = asorti(a,b);
for (i = 1; i <= n; i++) {
print b[i];
if (i%2 == 0) {
printf "\n";
}
}
}
' output.txt
Age 1
Age 2
Height 1
Height 2
Weight 1
Weight 2
решение3
Я предполагаю, что пустые строки не являются частью вашего фактического файла, или, по крайней мере, вас это не волнует. Если так, то все, что вам нужно, это sort
:
$ cat output.txt
Age 1
Height 1
Weight 1
Age 2
Height 2
Weight 2
$ sort output.txt
Age 1
Age 2
Height 1
Height 2
Weight 1
Weight 2
Однако, если ваши файлы не слишком велики для хранения в памяти, может быть проще сделать все за один шаг:
grep -whE 'Age|Height|Weight' *txt | sort > outfile
Вышеуказанный код будет искать Age
или Height
или Weight
во всех файлах, имена которых заканчиваются на txt
в текущем каталоге ( *txt
). -w
Означает «соответствовать только целым словам» (чтобы, например, Age
не совпадало ), необходим, поскольку без него имя файла печатается вместе с соответствующей строкой, когда указано более одного входного файла. Включает расширенные регулярные выражения, что дает нам для OR.Ageing
-h
-E
|
ПРИМЕЧАНИЕ: Если по какой-то причине вам действительно нужна дополнительная пустая строка между каждой записью (а это не то, что grep
создаст ваша команда), вы можете добавить ее с помощью:
grep -whE 'Age|Height|Weight' *txt | sort | sed 's/$/\n/'
Пример
$ for i in {1..3}; do echo -e "Name $i\nAge $i\nHeight $i\nWeight $i" > $i.txt; done
$ for f in *txt; do echo " -- $f --"; cat $f; done
-- 1.txt --
Name 1
Age 1
Height 1
Weight 1
-- 2.txt --
Name 2
Age 2
Height 2
Weight 2
-- 3.txt --
Name 3
Age 3
Height 3
Weight 3
$ grep -whE 'Age|Height|Weight' *txt | sort
Age 1
Age 2
Age 3
Height 1
Height 2
Height 3
Weight 1
Weight 2
Weight 3
В любом случае, даже если sort
это вас не устроит, я бы сделал вот так на Perl awk
(предполагая, что вам нужны дополнительные пустые строки, что, опять же, вам, скорее всего, не нужно):
$ perl -ane '$k{$F[0]}.=$_."\n" if /./;
END{print $k{$_},"\n" for sort keys (%k)}' output.txt
Age 1
Age 2
Height 1
Height 2
Weight 1
Weight 2
Вы можете пропустить это, head -n -2
чтобы избавиться от последних двух пустых строк, если они вам не нужны.
решение4
python
решение этой проблемы:
from collections import defaultdict
f = open("output.txt", "r")
d = defaultdict(list)
for line in f:
line = line.strip()
if line != '':
arr = line.split(" ")
d[arr[0]].append(arr[1])
print d.items()
Я выполнил хэширование, используя первый столбец, и поместил его в список.