Найдите определенные столбцы и замените следующий столбец конкретным значением с помощью gawk

Я пытаюсь найти все места, где мои данные имеют повторяющуюся строку, и удалить повторяющуюся строку. Кроме того, я ищу, где 2-й столбец имеет значение 90, и заменяю следующий 2-й столбец конкретным номером, который я обозначаю.

Мои данные выглядят так:

 #      Type    Response        Acc     RT      Offset    
   1      70  0    0   0.0000 57850
   2      31  0    0   0.0000 59371
   3      41  0    0   0.0000 60909
   4      70  0    0   0.0000 61478
   5      31  0    0   0.0000 62999 
   6      41  0    0   0.0000 64537
   7      41  0    0   0.0000 64537
   8      70  0    0   0.0000 65106
   9      11  0    0   0.0000 66627
  10      21  0    0   0.0000 68165
  11      90  0    0   0.0000 68700
  12      31  0    0   0.0000 70221

Я хочу, чтобы мои данные выглядели так:

 #      Type    Response        Acc     RT      Offset
   1      70  0    0   0.0000 57850
   2      31  0    0   0.0000 59371
   3      41  0    0   0.0000 60909
   4      70  0    0   0.0000 61478
   5      31  0    0   0.0000 62999
   6      41  0    0   0.0000 64537
   8      70  0    0   0.0000 65106
   9      11  0    0   0.0000 66627
  10      21  0    0   0.0000 68165
  11      90  0    0   0.0000 68700
  12       5  0    0   0.0000 70221

Мой код:

 BEGIN {
priorline = "";
ERROROFFSET = 50;
ERRORVALUE[10] = 1;
ERRORVALUE[11] = 2;
ERRORVALUE[12] = 3;
ERRORVALUE[30] = 4;
ERRORVALUE[31] = 5;
ERRORVALUE[32] = 6;

ORS = "\n";
}

NR == 1 {
print;
getline;
priorline = $0;
}

NF == 6 {

brandnewline = $0
mytype = $2
$0 = priorline
priorField2 = $2;   

if (mytype !~ priorField2) {
print;
priorline = brandnewline;
}

if (priorField2 == "90") {
    mytype = ERRORVALUE[mytype];
    }
}

END {print brandnewline}


##Here the parameters of the brandnewline is set to the current line and then the
##proirline is set to the line on which we just worked on and the brandnewline is
##set to be the next new line we are working on. (i.e line 1 = brandnewline, now
##we set priorline = brandnewline, thus priorline is line 1 and brandnewline takes
##on line 2) Next, the same parameters were set with column 2, mytype being the 
##current column 2 value and priorField2 being the same value as mytype moves to
##the next column 2 value.  Finally, we wrote an if statement where, if the value
##in column 2 of the current line !~ (does not equal) value of column two of the
##previous line, then the current line will be print otherwise it will just be
##skipped over.  The second if statement recognizes the lines in which the value
##90 appeared and replaces the value in column 2 with a previously defined
##ERRORVALUE set for each specific type (type 10=1, 11=2,12=3, 30=4, 31=5, 32=6).

Мне удалось успешно удалить повторяющиеся строки, однако я не могу выполнить следующую часть своего кода, которая заключается в замене значений, которые я указал в BEGIN как ERRORVALUES (10 = 1, 11 = 2, 12 = 3). , 30=4, 31=5, 32=6) с фактическими столбцами, содержащими это значение. По сути, я хочу просто заменить это значение в строке своим ERRORVALUE.

Если кто-нибудь может помочь мне с этим, я был бы очень благодарен.


person user1269741    schedule 14.03.2012    source источник


Ответы (5)


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

awk '
  BEGIN {
    ERRORVALUE[10] = 1
    # ... etc
  }

  # print the header
  NR == 1 {print; next}

  NR == 2 || $0 !~ prev_regex {
    prev_regex = sprintf("^\\s+\\w+\\s+%s\\s+%s\\s+%s\\s+%s\\s+%s",$2,$3,$4,$5,$6)
    if (was90) $2 = ERRORVALUE[$2]
    print
    was90 = ($2 == 90)
  }
'

Для строк, в которых изменен 2-й столбец, это нарушает форматирование строки:

 #      Type    Response        Acc     RT      Offset
   1      70  0    0   0.0000 57850
   2      31  0    0   0.0000 59371
   3      41  0    0   0.0000 60909
   4      70  0    0   0.0000 61478
   5      31  0    0   0.0000 62999
   6      41  0    0   0.0000 64537
   8      70  0    0   0.0000 65106
   9      11  0    0   0.0000 66627
  10      21  0    0   0.0000 68165
  11      90  0    0   0.0000 68700
12 5 0 0 0.0000 70221

Если это проблема, вы можете направить вывод gawk в column -t или, если вы знаете, что формат строки фиксирован, используйте printf() в программе awk.

person glenn jackman    schedule 14.03.2012
comment
Во-первых: Большое спасибо за ваш ответ, он был очень полезным. Также спасибо за столь быстрый ответ. Во-вторых: меня беспокоит, возможно ли, что вместо строки после того, как я увижу 90 в $ 2, я подставлю whats в $ 2 двумя строками раньше? В этом примере вы видите 90 в $2 в строке 11. Можно ли изменить $2 в строке 9 в соответствии с форматированием, описанным в BEGIN, и если да, то как мне это сделать? - person user1269741; 15.03.2012
comment
Я бы, наверное, сделал 2 прохода по вашему файлу: awk 'remove duplicate lines' | tac | awk 'replace $2 if value 2 lines before is 90' | tac -- tac - это удобная утилита для печати файла с последней строки на первую. В противном случае awk-скрипт превращается в беспорядок с необходимостью запоминать 2 предыдущие строки, следить за тем, чтобы 2 строки назад не были удалены и т. д. - person glenn jackman; 15.03.2012

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

v=99999
sed ':a;$!N;s/^\(\s*\S*\s*\)\(.*\)\s*\n.*\2/\1\2/;ta;s/^\(\s*\S*\s*\)   90 /\1'"$(printf "%5d" $v)"' /;P;D' file
 #      Type    Response        Acc     RT      Offset    
   1      70  0    0   0.0000 57850
   2      31  0    0   0.0000 59371
   3      41  0    0   0.0000 60909
   4      70  0    0   0.0000 61478
   5      31  0    0   0.0000 62999 
   6      41  0    0   0.0000 64537
   8      70  0    0   0.0000 65106
   9      11  0    0   0.0000 66627
  10      21  0    0   0.0000 68165
  11   99999  0    0   0.0000 68700
  12      31  0    0   0.0000 70221 
person potong    schedule 14.03.2012

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

awk 'BEGIN {
        ERROROFFSET = 50;
        ERRORVALUE[10] = 1;
        ERRORVALUE[11] = 2;
        ERRORVALUE[12] = 3;
        ERRORVALUE[30] = 4;
        ERRORVALUE[31] = 5;
        ERRORVALUE[32] = 6;
     }
     NR == 1 { print ; next }
     { if (a[$2 $6]) { next } else { a[$2 $6]++ }
       if ( $2 == 90) { print ; n++ ; next } 
       if (n>0) { $2 = ERRORVALUE[$2] ; n=0 }
       printf("% 4i% 8i%  3i% 5i% 9.4f% 6i\n", $1, $2, $3, $4, $5, $6)
     }' INPUTFILE

Посмотрите, как это работает, здесь, на ideone.com.

ИМО блок BEGIN очевиден. Затем происходит следующее:

  1. строка NR == 1 печатает самую первую строку (и переключается на следующую строку, также это правило применяется только к самой первой строке)
  2. Затем проверьте, видели ли мы уже любую строку с теми же 2-м и 6-м столбцами, и если да, переключитесь на следующую строку, иначе пометьте ее как видимую в массиве (используя объединенные значения столбцов как индексы, но обратите внимание что это может подвести вас, если у вас большие значения во 2-м и маленькие в 6-м (например, 2 0020 объединено с 20020 и то же самое для 20 020), поэтому вы можете добавить разделитель столбцов в индекс, например a[$2 "-" $6]. .. и вы можете использовать больше столбцов, чтобы проверить еще точнее)
  3. Если строка имеет 90 во втором столбце, печатает ее, флаги для переключения на следующую строку, а затем переключаются на следующую строку (во входном файле)
  4. На следующей строке проверяет 2-й столбец в ERRORVALUE и, если находит, заменяет его содержимое.
  5. Затем печатает отформатированную строку.
person Zsolt Botykai    schedule 14.03.2012

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

awk '!a[$2,$3,$4,$5,$6]++' file.txt

Затем вы должны отредактировать свои значения по желанию. Если вы хотите изменить значение 90 во втором столбце на 5000, попробуйте что-то вроде этого:

awk 'NR == 1 { print; next } { sub(/^90$/, "5000", $2); printf("%4i% 8i% 3i% 5i% 9.4f% 6i\n", $1, $2, $3, $4, $5, $6) }' file.txt

Вы можете видеть, что я украл оператор printf Zsolt (спасибо Zsolt!) для форматирования, но вы можете отредактировать его, если необходимо. Вы также можете направить вывод из первого оператора во второй для красивого однострочника:

cat file.txt | awk '!a[$2,$3,$4,$5,$6]++' | awk 'NR == 1 { print; next } { sub(/^90$/, "5000", $2); printf("%4i% 8i% 3i% 5i% 9.4f% 6i\n", $1, $2, $3, $4, $5, $6) }'

person Steve    schedule 14.03.2012

Предыдущие варианты работают по большей части, однако я бы сделал это так, просто и приятно. После просмотра других сообщений я считаю, что это было бы наиболее эффективным. Кроме того, это также позволяет использовать дополнительный запрос, добавленный OP в комментариях, чтобы строка после 90 была заменена переменной из двух строк ранее. Это делает все это за один проход.

BEGIN {
    PC2=PC6=1337
    replacement=5
}
{
    if( $6 == PC6 ) next
    if( PC2 == 90 ) $2 = replacement
    replacement = PC2
    PC2 = $2 
    PC6 = $6
    printf "%4s%8s%3s%5s%9s%6s\n",$1, $2, $3, $4, $5, $6
}

Пример ввода

   1      70  0    0   0.0000 57850
   2      31  0    0   0.0000 59371
   3      41  0    0   0.0000 60909
   4      70  0    0   0.0000 61478
   5      31  0    0   0.0000 62999 
   6      41  0    0   0.0000 64537
   7      41  0    0   0.0000 64537
   8      70  0    0   0.0000 65106
   9      11  0    0   0.0000 66627
  10      21  0    0   0.0000 68165
  11      90  0    0   0.0000 68700
  12      31  0    0   0.0000 70221

Пример вывода

   1      70  0    0 0.000000 57850
   2      31  0    0 0.000000 59371
   3      41  0    0 0.000000 60909
   4      70  0    0 0.000000 61478
   5      31  0    0 0.000000 62999
   6      41  0    0 0.000000 64537
   8      70  0    0 0.000000 65106
   9      11  0    0 0.000000 66627
  10      21  0    0 0.000000 68165
  11      90  0    0 0.000000 68700
  12      21  0    0 0.000000 70221
person J.D.    schedule 23.03.2012