Как отсортировать или переставить числа из нескольких столбцов в несколько строк [фиксировано в 4 столбца]? - PullRequest
0 голосов
/ 03 июля 2018

У меня есть 1 текстовый файл, который является test1.txt.

text1.txt содержит следующее:
Введите:

##[A1] [B1] [T1]  [V1] [T2]  [V2] [T3]  [V3] [T4]  [V4]## --> headers
    1  1000    0   100   10   200   20   300   30   400
              40   500   50   600   60   700   70   800
       1010    0   101   10   201   20   301   30   401
              40   501   50   601  
    2  1000    0   110   15   210   25   310   35   410
              45   510   55   610   65   710
       1010    0   150   10   250   20   350   30   450
              40   550  

Условие:
A1 и B1 -> для каждого A1 + (B1 + [Tn + Vn])
A1 должен быть в 1 столбце.
B1 должен быть в 1 столбце.
T1, T2, T3 и T4 должны быть в 1 столбце.
V1, V2, V3 и V4 должны быть в 1 столбце.

Как мне отсортировать это, как показано ниже?
Желание выхода:

##   A1    B1   Tn    Vn ## --> headers

      1  1000    0   100
                10   200
                20   300
                30   400
                40   500
                50   600
                60   700
                70   800
         1010    0   101
                10   201
                20   301
                30   401
                40   501
                50   601
      2  1000    0   110
                15   210
                25   310
                35   410
                45   510
                55   610
                65   710
         1010    0   150
                10   250
                20   350
                30   450
                40   550

Вот мой текущий код:
Первая попытка:
Ввод

cat test1.txt | awk ' { a=$1 b=$2 } { for(i=1; i<=5; i=i+1) { t=substr($0,11+i*10,5) v=substr($0,16+i*10,5) if( t ~ /^\ +[0-9]+$/ || t ~ /^[0-9]+$/ || t ~ /^\ +[0-9]+\ +$/ ){ printf "%7s %7d %8d %8d \n",a,b,t,v } }}' | less

Выход:

      1    1000      400        0 
     40     500      800        0 
   1010       0      401        0 
      2    1000      410        0 
   1010       0      450        0

Я пытаюсь использовать простую команду awk, но все еще не могу получить результат.
Кто-нибудь может мне помочь в этом?

Спасибо,
Am

Ответы [ 4 ]

0 голосов
/ 03 июля 2018

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

С GNU awk для FIELDWIDTHS для обработки полей фиксированной ширины это действительно не может быть намного проще:

$ cat tst.awk
BEGIN {
    # define the width of the input and output fields
    FIELDWIDTHS = "2 4 5 5 6 5 6 5 6 5 6 99"
    ofmt = "%2s%5s%6s%5s%6s%s\n"
}
{
    # strip leading/trailing blanks and square brackets from every field
    for (i=1; i<=NF; i++) {
         gsub(/^[[\s]+|[]\s]+$/,"",$i)
    }
}
NR==1 {
    # print the header line
    printf ofmt, $1, $2, $3, "Tn", "Vn", " "$NF
    next
}
{
    # print every other line
    for (i=4; i<NF; i+=2) {
        printf ofmt, $1, $2, $3, $i, $(i+1), ""
        $1 = $2 = $3 = ""
    }
}

.

$ awk -f tst.awk file
##   A1    B1   Tn    Vn ## --> headers
      1  1000    0   100
                10   200
                20   300
                30   400
                40   500
                50   600
                60   700
                70   800
         1010    0   101
                10   201
                20   301
                30   401
                40   501
                50   601
      2  1000    0   110
                15   210
                25   310
                35   410
                45   510
                55   610
                65   710
         1010    0   150
                10   250
                20   350
                30   450
                40   550

С другими awk вы бы использовали while() { substr() } цикл вместо FIELDWIDTHS, так что это было бы еще пара строк кода, но все еще тривиально.

Выше будет на порядок быстрее, чем эквивалентный сценарий оболочки. См https://unix.stackexchange.com/questions/169716/why-is-using-a-shell-loop-to-process-text-considered-bad-practice.

0 голосов
/ 03 июля 2018

Это довольно сложная проблема, которую можно решить несколькими способами. bash, perl или awk, вам нужно будет обработать количество полей некоторым полуобобщенным способом, а не просто жестко кодировать значения для вашего примера.

Используя bash, до тех пор, пока вы можете полагаться на четное количество полей во всех строках (кроме строк с единственным начальным значением (например, 1010), вы можете разместить количество полей достаточно разумно общего характера). способ. Для строк с 1, 2 и т. д. вы знаете, что ваш начальный вывод будет содержать 4-fields. Для строк с 1010 и т. д. вы знаете, что выход будет содержать начальный 3-fields. Для остальных значений вы просто выводите пар .

Хитрая часть обрабатывает выравнивание . Вот где printf, который позволяет вам установить ширина поля с параметром, используя форму "%*s", где спецификатор преобразования ожидает, что следующим параметром будет значение integer, указывающее field-width , за которым следует параметр для самого преобразования строки. Это займет немного гимнастики, но вы можете сделать что-то вроде следующего в bash:

(примечание: отредактируйте в соответствии с форматом выходного заголовка)

#!/bin/bash

declare -i nfields wd=6     ## total no. fields, printf field-width modifier

while read -r line; do      ## read each line  (preserve for header line)
    arr=($line)             ## separate into array
    first=${arr[0]}         ## check for '#' in first line for header
    if [ "${first:0:1}" = '#' ]; then
        nfields=$((${#arr[@]} - 2))     ## no. fields in header
        printf "##   A1    B1   Tn    Vn ## --> headers\n"  ## new header
        continue
    fi
    fields=${#arr[@]}                   ## fields in line
    case "$fields" in
        $nfields )                      ## fields -eq nfiles?
            cnt=4                       ## handle 1st 4 values in line
            printf " "
            for ((i=0; i < cnt; i++)); do
                if [ "$i" -eq '2' ]; then
                    printf "%*s" "5" "${arr[i]}"
                else
                    printf "%*s" "$wd" "${arr[i]}"
                fi
            done
            echo
            for ((i = cnt; i < $fields; i += 2)); do    ## handle rest
                printf "%*s%*s%*s\n" "$((2*wd))" " " "$wd" "${arr[i]}" "$wd" "${arr[$((i+1))]}"
            done
            ;;
        $((nfields - 1)) )              ## one less than nfields
            cnt=3                       ## handle 1st 3 values
            printf " %*s%*s" "$wd" " "
            for ((i=0; i < cnt; i++)); do
                if [ "$i" -eq '1' ]; then
                    printf "%*s" "5" "${arr[i]}"
                else
                    printf "%*s" "$wd" "${arr[i]}"
                fi
            done
            echo
            for ((i = cnt; i < $fields; i += 2)); do    ## handle rest
                if [ "$i" -eq '0' ]; then
                    printf "%*s%*s%*s\n" "$((wd+1))" " " "$wd" "${arr[i]}" "$wd" "${arr[$((i+1))]}"
                else
                    printf "%*s%*s%*s\n" "$((2*wd))" " " "$wd" "${arr[i]}" "$wd" "${arr[$((i+1))]}"
                fi
            done
            ;;
        * )     ## all other lines format as pairs
            for ((i = 0; i < $fields; i += 2)); do
                printf "%*s%*s%*s\n" "$((2*wd))" " " "$wd" "${arr[i]}" "$wd" "${arr[$((i+1))]}"
            done
            ;;
    esac
done

Вместо чтения из файла, просто используйте перенаправление, чтобы перенаправить входной файл в ваш скрипт (если вы хотите просто указать имя файла, затем перенаправить файл для подачи в выходной цикл while read...)

Пример использования / Вывод

$ bash text1format.sh <dat/text1.txt
##   A1    B1   Tn    Vn ## --> headers
      1  1000    0   100
                10   200
                20   300
                30   400
                40   500
                50   600
                60   700
                70   800
         1010    0   101
                10   201
                20   301
                30   401
                40   501
                50   601
      2  1000    0   110
                15   210
                25   310
                35   410
                45   510
                55   610
                65   710
         1010    0   150
                10   250
                20   350
                30   450
                40   550

Между awk и bash, awk обычно будет быстрее, но здесь с форматированным выводом он может быть ближе, чем обычно. Посмотрите вещи и дайте мне знать, если у вас есть вопросы.

0 голосов
/ 03 июля 2018

Это может сработать для вас (GNU sed):

sed -r '1d;s/^(.{11}).{11}/&\n\1/;s/^((.{5}).*\n)\2/\1     /;s/^(.{5}(.{6}).*\n.{5})\2/\1      /;/\S/P;D' file

Удалить первую строку (если нужен заголовок, см. Ниже). Ключевые поля занимают первые 11 (первый ключ - 5 символов, а вторые 6) - символы, а поля данных занимают следующие 11. Вставьте новую строку и ключевые поля перед каждой парой полей данных. Сравните ключи в соседних строках и замените их пробелами, если они дублируются. Не печатайте пустых строк.

Если нужен заголовок, используйте следующее:

sed -r '1{s/\[[^]]+\]\s*//5g;y/[]/  /;s/1/n/3g;s/B/ B/;G;b};s/^(.{11}).{11}/&\n\1/;s/^((.{5}).*\n)\2/\1     /;s/^(.{5}(.{6}).*\n.{5})\2/\1      /;/\S/P;D' file

Это делает дополнительное форматирование в первой строке, чтобы удалить лишние заголовки [], заменить 1 на n, добавить дополнительное пространство для выравнивания и следующую пустую строку.

Дальше больше. Используя вторую строку входного файла в качестве шаблона для данных, можно создать сценарий sed, который не имеет значений hard coded:

sed -r '2!d;s/\s*\S*//3g;s/.\>/&\n/;h;s/[^\n]/./g;G;s/[^\n.]/ /g;s#(.*)\n(.*)\n(.*)\n(.*)#1d;s/^(\1\2)\1\2/\&\\n\\1/;s/^((\1).*\\n)\\2/\\1\3/;s/^(\1(\2).*\\n\1)\\2/\\1\4/;/\\S/P;D#' file |
sed -r -f - file

Сценарий, созданный из шаблона, передается во второй вызов sed в виде файла и запускается с исходным файлом для получения требуемого вывода.

Аналогичным образом, заголовки могут быть отформатированы, если это необходимо:

sed -r '2!d;s/\s*\S*//3g;s/.\>/&\n/;h;s/[^\n]/./g;G;s/[^\n.]/ /g;s#(.*)\n(.*)\n(.*)\n(.*)#s/^(\1\2)\1\2/\&\\n\\1/;s/^((\1).*\\n)\\2/\\1\3/;s/^(\1(\2).*\\n\1)\\2/\\1\4/;/\\S/P;D#' file |
sed -r -e '1{s/\[[^]]+\]\s*//5g;y/[]/  /;s/1/n/3g;s/B/ B/;G;b}' -f - file

Путем извлечения первых четырех полей из второй строки входного файла можно создать четыре переменные. Два регулярных выражения и два значения. Эти переменные могут быть использованы для построения сценария sed.

N.B. Сценарий sed создается из строк, извлеченных из шаблона, а полученные переменные также являются строками, поэтому их можно объединять для получения новых новых регулярных выражений, новых значений и т. Д. И т. Д.

0 голосов
/ 03 июля 2018

Это нелегко, потому что трудно определить, когда у вас есть разные стили строки - те, у которых есть значения как в столбце 1, так и в столбце 2, те, у которых нет значения в столбце 1 и в столбце 2, и в столбце 1 или 2 значения нет. Первый шаг - сделать это проще - sed на помощь:

$ sed 's/[[:space:]]\{1,\}$//
s/^....../&|/
s/|....../&|/
:a
s/|\(  *[0-9][0-9]* \)\( *[^|]\)/|\1|\2/
t a' data
    1 | 1000 |   0 |  100 |  10 |  200 |  20 |  300 |  30 |  400
      |      |  40 |  500 |  50 |  600 |  60 |  700 |  70 |  800
      | 1010 |   0 |  101 |  10 |  201 |  20 |  301 |  30 |  401
      |      |  40 |  501 |  50 |  601
    2 | 1000 |   0 |  110 |  15 |  210 |  25 |  310 |  35 |  410
      |      |  45 |  510 |  55 |  610 |  65 |  710
      | 1010 |   0 |  150 |  10 |  250 |  20 |  350 |  30 |  450
      |      |  40 |  550
$

Первая строка удаляет все пробелы, чтобы избежать путаницы. Следующие два выражения обрабатывают столбцы фиксированной ширины 1 и 2 (по 6 символов в каждом). Следующая строка создает метку a; заменитель находит трубу |, несколько пробелов, несколько цифр, пробел и некоторый конечный материал, который не содержит трубы; и вставляет трубу посередине. t a возвращается к метке, если была сделана замена.

С этим становится легко управлять awk с разделителем полей |. Это многословно, но, кажется, делает свое дело:

awk -F '|' '
$1 > 0 { printf "%5d  %4d  %3d  %3d\n", $1, $2, $3, $4
         for (i = 5; i <= NF; i += 2) { printf "%5s  %4s  %3d  %3d\n", "", "", $i, $(i+1) }
         next
       }
$2 > 0 { printf "%5s  %4d  %3d  %3d\n", "", $2, $3, $4
         for (i = 5; i <= NF; i += 2) { printf "%5s  %4s  %3d  %3d\n", "", "", $i, $(i+1) }
         next
       }
       { for (i = 3; i <= NF; i += 2) { printf "%5s  %4s  %3d  %3d\n", "", "", $i, $(i+1) }
         next
       }'

Выход:

    1  1000    0  100
              10  200
              20  300
              30  400
              40  500
              50  600
              60  700
              70  800
       1010    0  101
              10  201
              20  301
              30  401
              40  501
              50  601
    2  1000    0  110
              15  210
              25  310
              35  410
              45  510
              55  610
              65  710
       1010    0  150
              10  250
              20  350
              30  450
              40  550

Если вам нужно удалить заголовки, добавьте 1d; в начало сценария sed.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...