Хороший способ сделать это — использовать ассоциативный массив или хэш. Ключом для каждого хэша является первое поле (. Я буду называть их «идентификаторами» из-за отсутствия лучшего термина ), а значения, хранящиеся для каждого ключа, будут либо строкой, содержащей запятую -, разделенную список значений, видимых для этого идентификатора, или массив, содержащий то же самое.
авк:
В этой версии awk используется строка, разделенная запятыми -, потому что (в awk )это проще, чем работать с ассоциативным массивом, содержащим массив.
#!/usr/bin/awk -f
BEGIN {
FS=" *, *";
OFS="";
}
{
key=$1; $1=""; $0=$0;
if (length(ids[key]) > 0) {
ids[key]=ids[key]","$0;
} else {
ids[key] = $0
};
}
END {
for (k in ids) {
print k "," ids[k]
}
}
В perl не сложнее (и намного полезнее/гибче )работать с хэшем массивов (или "HoA" ), чем с конкатенированной строкой:
#!/usr/bin/perl -w
use strict;
my %ids = ();
while(<>) {
chomp;
my @F = split /\s*,\s*/;
push @{ $ids{$F[0]} }, $F[1];
};
END {
foreach my $key (keys %ids) {
print $key. ','. join(",",@{ $ids{$key} }), "\n";
}
}
Вывод для версий awk и perl одинаков:
71w - Ukr,307200,51200
71w - Bar,1280000,102400,2048000
71w - Res,153600
71w - Upg,358400
71w - Sin,358400
71w - Mic,102400,51200
71w - May,20480
71w - Tha,512000,972800
71w - Jul,256000,51200
71w - Uni,51200
71w - Ind,50176,40960
71w - Pro,256000
71w - Rus,51200
71w - Leg,20480
71w - Phi,307200
Обратите внимание, :вывод как для awk, так и для perl версии не в каком-либо определенном порядке,и, вероятно, будет появляться в другом порядке каждый раз, когда вы его запускаете. Это потому, что awk "ассоциативные массивы" и perl "хэши" (два названия одного и того же )по своей сути неупорядочены.
При желании вы можете направить вывод на sort
. или в Perl вы можете использовать:
foreach my $key (sort keys %ids) {
вместо:
foreach my $key (keys %ids) {
Кроме того, -поскольку мы храним отдельные значения для каждого идентификатора в массиве, в Perl также легко сортировать эти значения. например. замените весь блок END
в версии Perl на:
END {
foreach my $key (sort keys %ids) {
print $key. ','. join(",",sort @{ $ids{$key} }), "\n";
}
}
и вывод будет:
71w - Bar,102400,1280000,2048000
71w - Ind,40960,50176
71w - Jul,256000,51200
71w - Leg,20480
71w - May,20480
71w - Mic,102400,51200
71w - Phi,307200
71w - Pro,256000
71w - Res,153600
71w - Rus,51200
71w - Sin,358400
71w - Tha,512000,972800
71w - Ukr,307200,51200
71w - Uni,51200
71w - Upg,358400
GNU AWK:
awk -v IGNORECASE=1 '
FNR==1 {n = split($0, col)}
{printf("%s ", $1); k=2
for(i=2; i<=n; i++)
printf("%s ", $0 ~ "\\<"col[i]"\\>"?$(k++):"")
print ""}
' file | column -ts' '
IGNORECASE=1
-Игнорировать регистр в шаблонахcolumn -ts' '
-разделитель ввода — один пробел, это значительно упрощает программу на awk.
GNU SED:
sed -r '
s/\s+/ /g
1{h;b};G
:1;s/( \S*)(:\S*)(.*)\1/\3\1\2/i;t1
s/\n\S*//
:2;s/ [^: ]+( |$)/ \1/;t2
' file | column -ts' '
К каждой строке добавляется первая строка заголовка, разделенная разделителем \n
. Столбцы с левой стороны, за исключением первого, заменяют соответствующий столбец с правой стороны. Остальные столбцы, не имеющие знака :
, заменяются пробелом.
Рекомендации по отладке sed-скриптов:
Добавьте флаг -n
sed -nr
и поместите команду l
в конец 3 строки-1{h;b};G;l
. Запустите скрипт, затем повторите все для 4 строки и так далее. l
команда -показать содержимое буфера (пространство шаблонов )с привязкой к концу буфера$
[awk обновлен]:
awk '
NR==1 {n = split($0, col)}
{k=1; for(i=1; i<=n; i++)
printf( "%s ", $0 ~ "\\<"col[i]?$(k++):"")
print ""}
' file | column -ts' '
Будет работать при совпадении начальных заголовков, но лучше писать полные заголовки (, например.insert_job days_somthing start_somting window term max_run_alarm must_somthing
)и используйте конец -из -привязки к слову"\\<"col[i]"\\>"
Если первый столбец никогда не бывает пустым и в качестве идентификатора используется уникальное имя, то можно оставить как есть:
awk '
NR==1 {n = split($0, col)}
{printf("%s ", $1); k=2
for(i=2; i<=n; i++)
printf("%s ", $0 ~ "\\<"col[i]?$(k++):"")
print ""}
' file | column -ts' '
col[]
-массив с именами столбцов; col[1] == "Name"
; col[2] == "Date"
; col[3] == "Time"
и так далее."\\<"
-начало слова привязка. Пример-"\\<"col[2]
равно"\\<Date"
Тернарный оператор-condition expression ? statement1 : statement2
Когда выражение условия возвращает значение true, выполняется оператор 1; в противном случае выполняется оператор2.
$0 ~ "\\<"col[i]?$(k++):""
-Следовательно, если текущая строка $0
содержит "\\<"col[2]
, то следующее поле $(k++)
печатается в том порядке, в котором оно появляется в текущей строке (, например. $2
в $0
), если нет, то пустое поле ""
.
[awk updated2] :Удалите завершающий пробел.
awk '
NR==1 {n = split($0, col)}
{printf("%s ", $1); k=2
for(i=2; i<=n; i++)
printf("%s%c", ($0~"\\<"col[i]?$(k++):""), (n>i?OFS:ORS))}
' file | column -ts' '
[awk update3] :Для изменения порядка полей.
awk '
NR==1 {n = split($0, col)}
{k=1; for(i=1; i<=n; i++)
A[i] = ($0~"\\<"col[i]?$(k++):"")
for(i in A) $i = A[i]
}
1' file | column -ts' '
Всякий раз, когда ваши данные содержат пары тег=значение, как вы делаете (, например. в Date:su,mo
тег Data
и значение su,mo
), лучше всего сначала создать массив для хранения этого сопоставления(tag2val[]
ниже ), а затем вы можете получить доступ/распечатать/сравнить значения только по их тегам (aka имена )в любом порядке. В этом случае мы просто печатаем значения в том порядке, в котором теги появляются в первой строке ввода, но с таким подходом мы могли бы легко сделать гораздо больше :
$ cat tst.awk
BEGIN { OFS="\t" }
NR==1 {
for (fldNr=1; fldNr<=NF; fldNr++) {
val = $fldNr
tag = tolower(val)
tag2val[tag] = val
tags[++numTags] = tag
}
}
NR > 1 {
tag2val[tags[1]] = $1
for (fldNr=2; fldNr<=NF; fldNr++) {
val = $fldNr
tag = tolower(val)
sub(/:.*/,"",tag)
tag2val[tag] = val
}
}
{
for (tagNr=1; tagNr<=numTags; tagNr++) {
tag = tags[tagNr]
val = tag2val[tag]
printf "%s%s", val, (tagNr<numTags ? OFS : ORS)
}
delete tag2val
}
$ awk -f tst.awk file | column -s$'\t' -t
Name Date Time Mxam Mxterm
Maxus Date:su,mo Time:12,3:00 mxam:20 Mxterm:10
Feros Time:12,3:00 Mxterm:19
Michel Mxterm:16
Это sed
решение,используя #
для визуализации пространств:
$ sed -E '1s/\s+/#/' file | \
tr -s ' ' '#' | \
sed -E '2,$s/#*(D[^#]*)*#*(T[^#]*)*#*(mx[^#]*)*#*(Mx[^#]*)*$/#\1#\2#\3#\4/' | \
tr '#' ' ' |\
column -nt
Name Date Time Mxam Mxterm
Maxus Date:su,mo Time:12,3:00 mxam:20 Mxterm:10
Feros Time:12,3:00 Mxterm:19
Michel Mxterm:16
Вот еще один способ переформатировать ваши данные.
Мы используем awk для генерации кода для утилиты tbl
, которая затем передается в текстовый процессор groff
для табулирования наших данных.
< file \
awk -v OFS='@' '
{ split($0, a) }
NR==1{
for (idx in a) b[tolower(a[idx])]=idx
print ".TS"; print "tab(", ");"
for (i=1; i<=NF; i++)
printf "%s%s", "l", (i<NF ? OFS : ".")
$1 = ORS $1
}
NR>1{
$0=""; $1=a[1]
for (i=2; i in a; i++) $(b[tolower(substr(a[i],1,-1+index(a[i],":")))]) = a[i]
}1
END { print ".TE" }
' | tbl | groff -Tascii | grep.
Выход:
Name Date Time Mxam Mxterm
Maxus Date:su,mo Time:12,3:00 mxam:20 Mxterm:10
Feros Time:12,3:00 Mxterm:19
Michel Mxterm:16