Переформатирование текста с помощью sed или awk

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

авк:

В этой версии 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
2
20.10.2021, 16:49
4 ответа

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-скриптов:
Добавьте флаг -nsed -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' '
2
20.10.2021, 19:38

Всякий раз, когда ваши данные содержат пары тег=значение, как вы делаете (, например. в 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
2
20.10.2021, 19:11

Это 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
0
20.10.2021, 19:46

Вот еще один способ переформатировать ваши данные.

Мы используем 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
0
22.10.2021, 00:56

Теги

Похожие вопросы