AWK: объединить и обработать три или более файлов методом, аналогичным подходу FNR==NR.

Обновлено окончательным кодом и желаемым результатом

в END блок Я создал новый массив arr4 с элементами, где оба arr1 а также arr2имеют общего, вдохновленные этим ответом. Получите общие значения в 2 массивах в сценариях оболочки.

(Для минимально воспроизводимого образца я уступаю место только arr1-arr2 пример здесь, и я расширю его самостоятельно с помощью arr2-arr3 arr1-arr3 arr1-arr2-arr3 коды)

      $ cat threefilepr.sh 
awk -v FS='[|]' '{  for(i=FNR ; i<=NR && i<=FNR && NR==FNR; i++) {x++; arr1[c++]=$2}
                    for(i=FNR ; i+x<=NR && i<=FNR && NR==FNR+x; i++) {y++; arr2[l++]=$3}
                    for(i=FNR ; i+x+y<=NR && i<=FNR && NR==FNR+x+y; i++) { arr3[m++]=$1}
         }  
        END {   for (c=0 ; c in arr1 ; c++) 
                    for (l=0 ; l in arr2 ; l++)  
                        if (arr1[c] == arr2[l]) 
                            arr4[k++] = arr1[c]
            
            for (k=1 ; k in arr4 ; k++) 
                print arr4[k]       }' file1 file2 file3

Текущий выход для этого кода

      $ sh threefilepr.sh 
A4

Желаемый результат

      $ sh threefilepr.sh 
A3    
A4

Опубликовать перед редактированием

Поскольку я учусь; Я обнаружил, что подход - это очень распространенный метод обработки двух файлов. Если FNR==NR; тогда это first file, когда FNR сбросить на 1 при чтении каждой строки из объединенных файлов это означает, и это, очевидно, second file.

Когда дело доходит до трех или более файлов, я не вижу способа, который является вторым и третьим файлом, поскольку оба имеют одинаковые !(FNR==NR)условие. Это заставило меня попытаться понять, как может быть что-то вроде и?

Итак, я реализовал метод обработки трех файлов в одном. Предполагая, что есть FNR1 FNR2 FNR3для каждого файла. Для каждого файла, который я сделал for loopэто работает отдельно. Условия одинаковы для каждого цикла NR==FNR# и действительно получил то, что ожидал:

AWK для цикла

          $ cat fnrarray.sh 
awk -v FS='[|]' '{ for(i=FNR ; i<=NR && i<=FNR && NR==FNR; i++)         {x++; print "NR:",NR,"FNR1:",i,"FNR:",FNR,"\tfirst file\t"}
                   for(i=FNR ; i+x<=NR && i<=FNR && NR==FNR+x; i++)     {y++; print "NR:",NR,"FNR2:",i+x,"FNR:",FNR,"\tsecond file\t"}
                   for(i=FNR ; i+x+y<=NR && i<=FNR && NR==FNR+x+y; i++) {print "NR:",NR,"FNR3:",i+x+y,"FNR:",FNR,"\tthird file\t"}
}' file1 file2 file3 

выход

      $ sh fnrarray.sh
NR: 1 FNR1: 1 FNR: 1    first file  
NR: 2 FNR1: 2 FNR: 2    first file  
NR: 3 FNR1: 3 FNR: 3    first file  
NR: 4 FNR1: 4 FNR: 4    first file  
NR: 5 FNR2: 5 FNR: 1    second file 
NR: 6 FNR2: 6 FNR: 2    second file 
NR: 7 FNR2: 7 FNR: 3    second file 
NR: 8 FNR3: 8 FNR: 1    third file  
NR: 9 FNR3: 9 FNR: 2    third file

Вы можете видеть, что это совпадает с FNR# и читается, что NR для чего file#.

Прошу просто из любопытства и будущих исследований:

Есть ли уже метод, дающий аналогичные результаты для awk?

Хотя я нашел этот метод FNR==1{++f} f==1 {}здесь Обработка 3 файлов с использованием awk

Но этот метод заменяет, когда новая строка читается каждый раз

Неудачная попытка 1

      $ awk -v FS='[|]' 'FNR==1{++f} f==1 {split($2,arr); print arr[1]}' file1 file2 file3 
A1
A2
A3
A4

Неудачная попытка 2

      $ awk -v FS='[|]' 'FNR==1{++f} f==1 {arr1[k++]=$2; print arr[1]}' file1 file2 file3
(empty line)
(empty line)
(empty line)
(empty line)

Успех с помощью цикла for ( arr1[1] не изменено)

      $ awk -v FS='[|]' '{for(i=FNR ; i<=NR && i<=FNR && NR==FNR; i++) {arr1[++k]=$2; print arr1[1]}}' file1 file2 file3 
A1
A1
A1
A1

Содержание файла

      $ cat file1
X|A1|Z
X|A2|Z
X|A3|Z
X|A4|Z
$ cat file2
X|Y|A3
X|Y|A4
X|Y|A5
$ cat file3
A1|Y|Z
A4|Y|Z

2 ответа

Чтобы идентифицировать файлы по порядку с помощью GNU awk, несмотря ни на что:

      awk '
    ARGIND == 1 { do 1st file stuff }
    ARGIND == 2 { do 2nd file stuff }
    ARGIND == 3 { do 3rd file stuff }
' file1 file2 file3

например, чтобы получить текст в разделе «вывод» в вашем вопросе из трех предоставленных вами исходных файлов:

      awk '
    ARGIND == 1 { pos = "first" }
    ARGIND == 2 { pos = "second" }
    ARGIND == 3 { pos = "third" }
    { print "NR:", NR, "FNR" ARGIND ":", NR, "FNR:", FNR, pos " file" }
' file1 file2 file3
NR: 1 FNR1: 1 FNR: 1 first file
NR: 2 FNR1: 2 FNR: 2 first file
NR: 3 FNR1: 3 FNR: 3 first file
NR: 4 FNR1: 4 FNR: 4 first file
NR: 5 FNR2: 5 FNR: 1 second file
NR: 6 FNR2: 6 FNR: 2 second file
NR: 7 FNR2: 7 FNR: 3 second file
NR: 8 FNR3: 8 FNR: 1 third file
NR: 9 FNR3: 9 FNR: 2 third file

или используя любой awk, если все имена файлов уникальны, независимо от того, пусты они или нет:

      awk '
    FILENAME == ARGV[1] { do 1st file stuff }
    FILENAME == ARGV[2] { do 2nd file stuff }
    FILENAME == ARGV[3] { do 3rd file stuff }
' file1 file2 file3

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

      awk '
    FNR == 1 { argind++ }
    argind == 1 { do 1st file stuff }
    argind == 2 { do 2nd file stuff }
    argind == 3 { do 3rd file stuff }
' file1 file2 file1

если имена файлов могут появляться несколько раз в списке аргументов и некоторые из файлов могут быть пустыми, тогда становится сложнее с awk, отличным от GNU, поэтому в GNU awk есть ARGIND, например, что-то вроде (непроверено):

      awk '
    BEGIN {
        for (i=1; i<ARGC; i++) {
            fname = ARGV[i]
            if ( (getline line < fname) > 0 ) {
                # file is not empty so save its position in the args
                # list in an array indexed by its name and the number
                # of times that name has been seen so far
                arginds[fname,++tmpcnt[fname]] = i
            }
            close(fname)
        }
    }
    FNR == 1 { argind = arginds[FILENAME,++cnt[FILENAME]] }
    argind == 1 { do 1st file stuff }
    argind == 2 { do 2nd file stuff }
    argind == 3 { do 3rd file stuff }
' file1 file2 file1

Когда дело доходит до трех или более файлов, я не вижу способа, который является вторым и третьим файлом, поскольку оба имеют одинаковое условие!(FNR==NR). Это заставило меня попытаться понять, как могут существовать что-то вроде FNR2 и FNR3?

Вот пример:

      $ cat f1
X|A1|Z
X|A2|Z
X|A3|Z
X|A4|Z

$ cat f2
X|Y|A3
X|Y|A4
X|Y|A5

$ cat f3
A1|Y|Z
A4|Y|Z

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

      $ awk -F '|' 'FNR==1{file++}{array[file, FNR]=$0; max=max>FNR?max:FNR}END{for(f=1; f<=file; f++){ for(row=1; row<=max; row++){ key=f SUBSEP row; if(key in array)print "file: "f,"row :"row,"record: "array[key]   } }}' f1 f2 f3
file: 1 row :1 record: X|A1|Z
file: 1 row :2 record: X|A2|Z
file: 1 row :3 record: X|A3|Z
file: 1 row :4 record: X|A4|Z
file: 2 row :1 record: X|Y|A3
file: 2 row :2 record: X|Y|A4
file: 2 row :3 record: X|Y|A5
file: 3 row :1 record: A1|Y|Z
file: 3 row :2 record: A4|Y|Z

Объяснение:

      awk -F '|' 'FNR==1{                   # FNR will reset for every file
              file++                  # so whenever FNR==1 increment variable file
            }
            {
              # array name      : array
              # array key being : file, FNR
              # array value     : $0 which current record/row
              array[file, FNR] = $0; 
              # here we find which row count in all available files
              max = max > FNR ? max : FNR
            }

            END{                     # end block when all files are read
             # start iterating over file
             # as we now variable file hold total no files read
             for(f=1; f<=file; f++)
             { 
                  # iterate now for record from each file
                  # variable max holds max row count
                  for(row=1; row<=max; row++)
                  { 
                      # variable key will now have
                      # key = file-number SUBSET row-number
                      key=f SUBSEP row; 
                      # if key exists in array 
                      # print array value
                      if(key in array)
                           print "file: "f,"row :"row,"record: "array[key] 
                  } 
             }
            }' f1 f2 f3

Другой вариант - использовать настоящие многомерные массивы, как показано ниже. gawk конкретные конечно.

Предполагая, что имена файлов уникальны, в противном случае используйте FNR==1{ file++} и вместо FILENAME использовать file

      $ awk --version
GNU Awk 4.2.1, API: 2.0 (GNU MPFR 3.1.6-p2, GNU MP 6.1.2)
Copyright (C) 1989, 1991-2018 Free Software Foundation.

$ awk -F '|' '{
              true_multi_array[FILENAME][FNR] = $0
            }
            END{
              for(file in true_multi_array)
                for(row in true_multi_array[file]) 
                  print  "file:",file, "row :" row, "record:" true_multi_array[file][row] 
            }' f1 f2 f3
file: f1 row :1 record:X|A1|Z
file: f1 row :2 record:X|A2|Z
file: f1 row :3 record:X|A3|Z
file: f1 row :4 record:X|A4|Z
file: f2 row :1 record:X|Y|A3
file: f2 row :2 record:X|Y|A4
file: f2 row :3 record:X|Y|A5
file: f3 row :1 record:A1|Y|Z
file: f3 row :2 record:A4|Y|Z
Другие вопросы по тегам