Преобразуйте key=value блоки в CSV

Похож ex запускается в последней строке - ex -c '1' -c '?bar' -c 'visual' test.txt перемещает курсор в последнюю строку.

4
25.11.2018, 16:36
5 ответов

Решение bash :

declare -a out

EOF=false
IFS=$'='

until $EOF; do
  read -r skip val || EOF=true
  if [ ! -z "$val" ]
  then
    out+=("${val//[[:space:]]/}")
  else
    tmp="${out[@]}"
    printf '%s\n' "${tmp// /,}"
    out=()
  fi
done < file

Как это работает

  • Объявить массив out для хранения выходной строки, установить переменную EOF , чтобы отслеживать конец файла, IFS для разделителя входных полей для чтения .
  • Пока мы не прочитаем конец файла, мы читаем каждую строку файла, устанавливаем значение последнего поля в переменную val .
  • если [! -z "$ val"] : проверить, не равна ли длина переменной $ val нулю, мы удаляем пробел в $ val , помещаем его в массив out .
  • если длина $ val равна нулю, что означает, что мы получаем пустую строку или конец файла, мы назначаем весь элемент массива out переменной tmp, затем заменяем всю пространственную переменную tmp от , , нашего разработанного разделителя перекодирования вывода.
  • Установите из в ноль для следующей работы.

Другое решение, более краткое, более короткое для вас - использование perl :

$ perl -F'=' -anle '
    BEGIN { $, = "," }
    push @out,$F[-1] if @F;
    print @{[map {s/\s// && $_} @out]} and @out = ()
        if /^$/ or eof;
' file
3,404212109727229,A24AD11812232B47688ADBF15CE05CA9,1,SIM,COMP128_3
3,404212109727230,A24AD11812232B47688ADBF15CE05CB8,1,SIM,COMP128_3
3,404212109727231,A24AD11812232B47688ADBF15CE05CD6,1,SIM,COMP128_3
4
27.01.2020, 20:45

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

awk -v RS= -v ORS=',' '{print $3, $6, $9, $12, $15, $18}'

Если вы можете полагаться на порядок и наличие полей, но не на пробелы, то вам понадобится немного разобрать, чтобы разделить поля на равные знаки.

awk -v RS= -F '\n' '{
    for (i = 1; i <= NF; i++) {
        sub(/[^=]*= */, "", $i);
        printf "%s%s", $i, (i==NF ? "\n" : ",");
    }
  }'

Вот метод Perl:

perl -000 -ne '
    $, = ","; $\ = "\n";
    @kv = split /\n| *= */;
    print @kv[grep {$_%2} 0..$#kv];
  '

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

awk -v OFS=',' '
    match($0, / *= */) {a[substr($0,1,RSTART-1)] = substr($0,RSTART+RLENGTH)}
    /^$/ {print a["HLRSN"], a["IMSI"], a["KIVALUE"], a["K4SNO"], a["CARDTYPE"], a["ALG"]; split("", a)}
    END {print a["HLRSN"], a["IMSI"], a["KIVALUE"], a["K4SNO"], a["CARDTYPE"], a["ALG"]}
'

Это одна строка в Perl.

perl -000 -F'/\n|\s*=\s*/' -ane '%F = @F; $\ = "\n"; $, = ","; print @F{qw(HLRSN IMSI KIVALUE K4SNO CARDTYPE ALG)}'
0
27.01.2020, 20:45

Сохраните следующее в файл (например, split.awk )

BEGIN {
RS="\n\n";
FS="\n";
ORS=",";
}

{
    for (i=1;i<=NF;i++)
    {
        split($i, sf, "= ")
        print sf[2]
    }
    printf "\n"
 }

Затем запустите:

awk -f split.awk Test.txt

Или запустите всю команду как одну строку:

awk 'BEGIN {RS="\n\n";FS="\n";ORS=",";}{for(i=1;i<=NF;i++){split($i, sf, "= ")print sf[2]}printf "\n"}' Test.txt

Это работает следующим образом:

  • Блок BEGIN запускается один раз в начале и устанавливает запись разделитель ( RS ) для двух символов новой строки и разделитель полей ( FS ) для одной новой строки. Разделитель выходных записей ( ORS ) установлен в запятую.

  • Затем он перебирает каждое поле в записи ( NF - количество полей в текущей записи) и разбивает его на «=».

  • Затем он печатает правую часть этого разделения с запятой между ними ( ORS )

  • После каждой строки он печатает новую строку, чтобы дать вам формат CSV.

3
27.01.2020, 20:45

Просто:

awk -v RS= -v OFS=, '{print $3,$6,$9,$12,$15,$18}'

Пустой разделитель записей ( RS = ) включает режим абзацев , в котором записи разделяются последовательности пустых строк. Внутри записи применяется разделитель полей по умолчанию (записи разделяются пробелами), поэтому в каждой записи интересующие нас поля - это 3-е, 6-е, 9-е ...

Мы изменяем вывод разделитель полей на запятую ( OFS =, ) и вывести интересующие нас поля.

10
27.01.2020, 20:45

Основываясь на других ответах... Мне потребовалась небольшая модификация, поскольку мне нужен был заголовок столбца в конечном CSV-файле. Вот мое awk-решение:

awk -v RS= -v FS='\n' '
    NR==1 {
      for (i=1; i<=NF; i++) {
        split($i,line,"= "); 
        printf "%s%s", line[1], (i==NF ? "\n" : ",");
      }
    } 
    {
      for (i=1; i<=NF; i++) {
        split($i,line,"= "); 
        printf "%s%s", line[2], (i==NF ? "\n" : ",");
      } 
    }'  

Как и в других ответах, здесь читается весь блок (до пустой строки) по полям, затем каждое поле снова разделяется с помощью split(). Единственная разница в том, что для первой записи печатается часть до '=', а во втором цикле for-loop печатается правая часть для всех записей.

1
27.01.2020, 20:45

Теги

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