Форматирование данных в виде таблицы

Думаю, это работает:

$ grep -i 'name="andy" remote="origin" branch=".*\"' <filename> | awk -F' ' '{print $5}' | sed -E 's/branch=\"(.*)\"/\1/'
master

Часть awkгарантирует, что возвращается только branch="master", часть sedвозвращает то, что находится между двойными кавычками со ссылкой (, \1соответствует части, заключенной в круглые скобки ).

Теперь я знаю, что здесь много людей с гораздо большими знаниями об искусстве awk и sed, так что я готов к некоторой критике:-)

4
16.05.2021, 10:36
5 ответов

Использование любого awk в любой оболочке на каждом сервере Unix:

$ cat tst.awk
BEGIN {
    numTags = split("Name City Age Couse",nums2tags)
    for (tagNr=1; tagNr<=numTags; tagNr++) {
        tag = nums2tags[tagNr]
        tags2nums[tag] = tagNr
        wids[tagNr] = ( length(tag) > length("null") ? length(tag) : length("null") )
    }
    OFS=" | "
}
(NR==1) || (prevTag=="Couse") {
    numRecs++
}
{
    gsub(/^"|"$/,"")
    tag = val = $0
    sub(/".*/,"",tag)
    sub(/[^"]+":"/,"",val)

    tagNr = tags2nums[tag]
    vals[numRecs,tagNr] = val

    wid = length(val)
    wids[tagNr] = ( wid > wids[tagNr] ? wid : wids[tagNr] )

    prevTag = tag
}
END {
    # Uncomment these 3 lines if youd like a header line printed:
    # for (tagNr=1; tagNr<=numTags; tagNr++) {
    #   printf "%-*s%s", wids[tagNr], nums2tags[tagNr], (tagNr<numTags ? OFS : ORS)
    # }

    for (recNr=1; recNr<=numRecs; recNr++) {
        for (tagNr=1; tagNr<=numTags; tagNr++) {
            val = ( (recNr,tagNr) in vals ? vals[recNr,tagNr] : "null" )
            printf "%-*s%s", wids[tagNr], val, (tagNr<numTags ? OFS : ORS)
        }
    }
}

$ awk -f tst.awk file
asxadadad ,aaf dsf | Mum  | 23  | BBS
null                | Ors  | 11  | MB
adad sf             | Kol  | 21  | BB
pqr                 | null | 21  | NN

или если вы не хотите использовать жесткий -закодированный список тегов (имена полей/столбцов):

$ cat tst.awk
BEGIN { OFS=" | " }
(NR==1) || (prevTag=="Couse") {
    numRecs++
}
{
    gsub(/^"|"$/,"")
    tag = val = $0
    sub(/".*/,"",tag)
    sub(/[^"]+":"/,"",val)

    if ( !(tag in tags2nums) ) {
        tagNr = ++numTags
        tags2nums[tag] = tagNr
        nums2tags[tagNr] = tag
        wids[tagNr] = ( length(tag) > length("null") ? length(tag) : length("null") )
    }

    tagNr = tags2nums[tag]
    vals[numRecs,tagNr] = val

    wid = length(val)
    wids[tagNr] = ( wid > wids[tagNr] ? wid : wids[tagNr] )

    prevTag = tag
}
END {
    for (tagNr=1; tagNr<=numTags; tagNr++) {
        printf "%-*s%s", wids[tagNr], nums2tags[tagNr], (tagNr<numTags ? OFS : ORS)
    }

    for (recNr=1; recNr<=numRecs; recNr++) {
        for (tagNr=1; tagNr<=numTags; tagNr++) {
            val = ( (recNr,tagNr) in vals ? vals[recNr,tagNr] : "null" )
            printf "%-*s%s", wids[tagNr], val, (tagNr<numTags ? OFS : ORS)
        }
    }
}

$ awk -f tst.awk file
Name                | City | Age | Couse
asxadadad ,aaf dsf | Mum  | 23  | BBS
null                | Ors  | 11  | MB
adad sf             | Kol  | 21  | BB
pqr                 | null | 21  | NN

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

4
28.07.2021, 11:32

В перл. Я бы добавил больше объяснений того, что он делает/как он работает, но я думаю, что комментарии в коде охватывают все это.

#!/usr/bin/perl

use strict;

my @people; # Array-of-Arrays (AoA) to hold each record
my %person; # hash to hold the current record as it's being read in.

# list of valid field names, in the order you want them printed
my @names   = qw(Name City Age Couse);
my $end_key = 'Couse';

# build a regex from the valid names
my $names    = join('|',@names);
my $names_re = qr/^(?:$names)$/;

# Initialise field widths, with a minimum of 4 (for 'null').
my %widths = map {$_ => (length > 4 ? length : 4) } @names;

while(<>) {
  chomp;

  s/^"|"$//g;                       # strip leading and trailing quotes
  my ($key,$val) = split /"?:"?/;   # split on :, with optional quotes.

  if ($key =~ m/$names_re/) {
    $widths{$key} = length($val) if ($widths{$key} < length($val) );

    $person{$key} = $val;

    if ($key eq $end_key) {
      # push an array into the @people array, containing the values of
      # the valid fields, in order.  Use null as the default value
      # if any field is empty/undefined.
      push @people, [ map { $person{$_} || 'null' } @names ];
      %person = ();
    };
  } else {
    print STDERR "Error on input line $.: unrecognised data\n";
  };
};

# build a printf format string, using the longest width of each field.
my $fmt = join(' | ', map { "%-$widths{$_}s" } @names). "\n";

# optional header line, comment out if not wanted
printf $fmt, @names;

# optional ruler line, comment out if not wanted
print join('-|-', map { '-' x $widths{$_} } @names). "\n";

foreach my $p (@people) {
  printf $fmt, @{ $p };
}

Сохранить как, например, columns.plи сделать исполняемым с помощью chmod +x.

Выход:

$ chmod +x columns.pl 
$./columns.pl demo.txt 
Name                | City | Age  | Couse
--------------------|------|------|------
asxadadad ,aaf dsf | Mum  | 23   | BBS 
null                | Ors  | 11   | MB  
adad sf             | Kol  | 21   | BB  
pqr                 | null | 21   | NN  
4
28.07.2021, 11:32

Использование массивов bash , в которых мы храним имена полей заголовков. Поскольку код sed повторяется для различных полей заголовка, мы сначала генерируем код sed на основе заголовка. Затем сгенерируйте вывод.

#!/usr/bin/env bash
declare -a tags=(Name City Age Couse end)
{
echo 'H;/^"Couse":/!d;z;x'
paste -d'\n' \
  <(printf 's/\\n"%s":"([^"]*)"(.*)/\\2|\\1/\n' "${tags[@]:0:4}") \
  <(printf 't %s\n' "${tags[@]:1:4}") \
  <(yes 's/$/|null/'|head -n4) \
  <(printf ':%s\n' "${tags[@]:1:4}");
echo 's/^\|//'
} |
sed -Ef - demo.txt |
column -ts'|' -o '|' |
sed -e 's/|/ & /g'

Выход:

asxadadad ,aaf dsf | Mum  | 23 | BBS 
null                | Ors  | 11 | MB  
adad sf             | Kol  | 21 | BB  
pqr                 | null | 21 | NN  

Здесь описан другой метод с использованием утилиты perl .

perl -lne '
  @tags = qw(Name City Age Couse) if $. == 1;
  %h = (%h, /"([^:]+)":"(.*)"/);
  next unless /^"Couse":/;
  for (@tags) {
    $h{$_} ||= q(null);
    length($h{$_})>$maxw{$_} and $maxw{$_}=length($h{$_});
    push @{$Aref->[$nr]}, $h{$_};
  }
  $nr++; %h=();
  }{
  my $fmt = join "A2", map { sprintf q[A%d], $_+1 } @maxw{@tags};
  print pack $fmt, split /([|])/, join q[|], @{shift @$Aref} while @$Aref;
' demo.txt
1
28.07.2021, 11:32

Использование Raku (, ранее известного как Perl _6)

Ниже приведено довольно быстрое -и -грязное решение на языке программирования Raku. Сохраните приведенный ниже код в файл с расширением .raku(.p6, в настоящее время также работает ):

.
my @a1= $*ARGFILES.IO.lines;
#my @a1= "/Can/use/hardcoded/path/to/file.txt".IO.lines;
@a1.=map(*.split(":")); 

my @a2; my $i=0, my $j=0;
my $b = << \"Name\" \"City\" \"Age\" \"Couse\" >> ;

while 
$i < (@a1.elems) { 
my $tag = $b[$j]; 
if @a1[$i][0] ~~ / <$tag> / {
    #`{above RHS matches inside double quotes}
    @a2.push(@a1[$i]);  
    $i++; $j=(($j+1) mod 4);}
    else 
    {@a2.push((<<$tag>>; <<\"NULL\">>));  
    $j=(($j+1) mod 4);}
};
.say for @a2;

"____________\n".say;

my @b;
loop (my $n=0; $n < $b.elems; $n++) { 
    my $tag0 := $b[$n];
    @b.push: @a2.grep(/<$tag0>/).[0..*-1]>>.[1];
};
$b.say;.say for @b;

"____________\n".say;
    
put $b.fmt('%s|').join("\n");
put ([Z] @b)>>.fmt('%s|').join("\n");    

Запустите приведенный выше сценарий в командной строке терминала, указав имя вашего файла -из -интереса (, например.~$ raku make_table.p6 input.txt). Выберите один из трех доступных выходов (, т. е. закомментируйте -два других ). Вывод первой секции (строка .say for @a2;выше )заполняет значения NULLдля 4 ключей, давая:

("Name" "asxadadad ,aaf dsf")
("City" "Mum")
("Age" "23")
("Couse" "BBS")
("Name" "NULL")
("City" "Ors")
("Age" "11")
("Couse" "MB")
("Name" "adad sf")
("City" "Kol")
("Age" "21")
("Couse" "BB")
("Name" "pqr")
("City" "NULL")
("Age" "21")
("Couse" "NN")

Выход средней секции (между двумя разделителями горизонтальных строк )дает:

("Name" "City" "Age" "Couse")
("asxadadad ,aaf dsf" "NULL" "adad sf" "pqr")
("Mum" "Ors" "Kol" "NULL")
("23" "11" "21" "21")
("BBS" "MB" "BB" "NN")

Вывод нижней секции (ниже второго горизонтального разделителя строк )при передаче через column -ts'\"'в командной строке bash (с использованием довольно -старой подсистемы unix ), дает:

Name                         |                    City  |     Age  |        Couse  |
asxadadad ,aaf dsf          |                    Mum   |     23   |        BBS    |
NULL                         |                    Ors   |     11   |        MB     |
adad sf                      |                    Kol   |     21   |        BB     |
pqr                          |                    NULL  |     21   |        NN     |

Два основных преимущества использования Raku для решения этой задачи: 1 )расширенная поддержка кавычек и 2 )поддержка Unicode. Добавление Raku в вашу среду программирования позволяет вам сохранить ваши прежние -существующие инструменты Unix/Linux --у вас просто есть дополнительный инструмент, когда он вам нужен.

[Выше протестировано на GNU bash, версия 3.2.57 (1 )-релиз (x86 _64 -яблоко -darwin14 )]
https://raku.org/

0
28.07.2021, 11:32

Данные выглядят так, как будто они были изменены по сравнению с тем, что изначально было каким-то документом JSON.

Вернем структуру документа JSON,

  1. добавление [{в начало документа и }]в конец,
  2. добавление },{в конец каждой строки, начинающейся с точной строки "Couse"(, но не в последнюю строку ), и
  3. добавление запятых в конец каждой строки, конец которой не изменен другим образом (т. е. в конце строки все еще есть двойная кавычка ).
sed -e '1 s/^/[{/' -e '$ s/$/}]/' \
    -e '/^"Couse"/ { $! s/$/},{/; }' \
    -e 's/"$/&,/' file

При красивой -печати наш документ превращается в

[
  {
    "Name": "asxadadad ,aaf dsf",
    "City": "Mum",
    "Age": "23",
    "Couse": "BBS"
  },
  {
    "City": "Ors",
    "Age": "11",
    "Couse": "MB"
  },
  {
    "Name": "adad sf",
    "City": "Kol",
    "Age": "21",
    "Couse": "BB"
  },
  {
    "Name": "pqr",
    "Age": "21",
    "Couse": "NN"
  }
]

Затем мы можем преобразовать это в CSV, передав его через jq(, добавив некоторые заголовки столбцов и заменив пустые значения строкой nullпри этом):

jq -r '    [ "Name", "City", "Age", "Couse" ],
    (.[] | [.Name, .City, .Age, .Couse  ]) |
    map(. // "null") | @csv'

Это приведет к

"Name","City","Age","Couse"
"asxadadad ,aaf dsf","Mum","23","BBS"
"null","Ors","11","MB"
"adad sf","Kol","21","BB"
"pqr","null","21","NN"

Затем мы можем использовать csvlookиз инструментария csvkitдля создания красивой таблицы.

Окончательный конвейер будет выглядеть так

sed -e '1 s/^/[{/' -e '$ s/$/}]/' \
    -e '/^"Couse"/ { $! s/$/},{/; }' \
    -e 's/"$/&,/' file |
jq -r '    [ "Name", "City", "Age", "Couse" ],
    (.[] | [.Name, .City, .Age, .Couse  ]) |
    map(. // "null") | @csv' |
csvlook --blanks

Мы используем csvlookс его опцией --blanks, чтобы сохранить строки nullтакими, какие они есть (, в противном случае эти )были бы удалены.

Результатом будет

| Name                | City | Age | Couse |
| ------------------- | ---- | --- | ----- |
| asxadadad ,aaf dsf | Mum  |  23 | BBS   |
| null                | Ors  |  11 | MB    |
| adad sf             | Kol  |  21 | BB    |
| pqr                 | null |  21 | NN    |

Или, представленный как уценка:

Имя Город Возраст Коуз
asxadadad,aaf dsf Мама 23 ББС
ноль Орс 11 МБ
Адад СФ Кол 21 ББ
пгр ноль 21 НН
0
28.07.2021, 11:32

Теги

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