Есть ли поле, в котором хранится точный разделитель полей FS, используемый в регулярном выражении, эквивалентный RT для RS?

Jan 04 2021

В GNU Awk 4.1.2 Разделение записи с помощьюgawk мы можем прочитать:

Когда RSявляется одиночным символом, RTсодержит тот же единственный символ. Однако, когда RSявляется регулярным выражением, RTсодержит фактический входной текст, соответствующий регулярному выражению.

Эта переменная RTочень полезна в некоторых случаях .

Точно так же мы можем установить регулярное выражение в качестве разделителя полей. Например, здесь мы позволяем использовать либо ";" или "|":

$ gawk -F';' '{print NF}' <<< "hello;how|are you" 2 # there are 2 fields, since ";" appears once $ gawk -F'[;|]' '{print NF}' <<< "hello;how|are you"
3  # there are 3 fields, since ";" appears once and "|" also once

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

$ gawk -F'[;|]' '{for (i=1;i<=NF;i++) printf ("%s%s", $i, FS)}' <<< "hello;how|are you"
hello[;|]how[;|]are you[;|]  # a literal "[;|]" shows in the place of FS

Есть ли способ «переупаковать» поля, используя специальный разделитель полей, используемый для разделения каждого из них, аналогично тому, что позволяет делать RT?

(примеры, приведенные в вопросе, довольно просты, но просто для того, чтобы показать суть)

Ответы

8 anubhava Jan 04 2021 at 16:34

Есть ли способ «переупаковать» поля, используя специальный разделитель полей, используемый для разделения каждого из них

Использование gnu-awk split()этого имеет дополнительный 4-й параметр для согласованного разделителя с использованием предоставленного регулярного выражения:

s="hello;how|are you"
awk 'split($0, flds, /[;|]/, seps) {for (i=1; i in seps; i++) printf "%s%s", flds[i], seps[i]; print flds[i]}' <<< "$s"

hello;how|are you

Более читаемая версия:

s="hello;how|are you"
awk 'split($0, flds, /[;|]/, seps) { for (i=1; i in seps; i++) printf "%s%s", flds[i], seps[i] print flds[i] }' <<< "$s"

Обратите внимание на 4 - й sepsпараметр в splitкотором хранится массив согласованного текста с помощью регулярных выражений , используемых в 3 - е параметра т /[;|]/.

Конечно, это не так коротко и просто, как RS, ORSи RT, которое можно записать как:

awk -v RS='[;|]' '{ORS = RT} 1' <<< "$s"
5 EdMorton Jan 04 2021 at 22:41

Как упоминает @anubhava , gawk имеет split()patsplit()это FPATкак split()есть FS- см.https://www.gnu.org/software/gawk/manual/gawk.html#String-Functions) делать то, что хочешь. Если вам нужна такая же функциональность с POSIX awk, тогда:

$ cat tst.awk function getFldsSeps(str,flds,fs,seps, nf) { delete flds delete seps str = $0

    if ( fs == " " ) {
        fs = "[[:space:]]+"
        if ( match(str,"^"fs) ) {
            seps[0] = substr(str,RSTART,RLENGTH)
            str = substr(str,RSTART+RLENGTH)
        }
    }

    while ( match(str,fs) ) {
        flds[++nf] = substr(str,1,RSTART-1)
        seps[nf]   = substr(str,RSTART,RLENGTH)
        str = substr(str,RSTART+RLENGTH)
    }

    if ( str != "" ) {
        flds[++nf] = str
    }

    return nf
}

{
    print
    nf = getFldsSeps($0,flds,FS,seps)
    for (i=0; i<=nf; i++) {
        printf "{%d:[%s]<%s>}%s", i, flds[i], seps[i], (i<nf ? "" : ORS)
    }
}

Обратите внимание на конкретную обработку вышеупомянутого случая, когда разделитель полей используется, " "потому что это означает 2 вещи, отличные от всех других значений разделителя полей:

  1. Поля фактически разделены цепочками любых пробелов, и
  2. Начальные пробелы должны игнорироваться при заполнении $ 1 (или flds [1] в этом случае), и поэтому пробелы, если они существуют, должны быть захвачены в seps [0] `для наших целей, поскольку каждый seps [N] связан с предшествующими ему полями [N].

Например, выполнив вышеуказанное для этих трех входных файлов:

$ head file{1..3}
==> file1 <==
hello;how|are you

==> file2 <==
hello how are_you

==> file3 <==
    hello how are_you

мы получим следующий вывод, в котором каждое поле отображается как номер поля, затем значение поля внутри, [...]затем разделитель внутри <...>, все внутри {...}(обратите внимание, что seps[0]заполняется IFF, FS " "и запись начинается с пробела):

$ awk -F'[,|]' -f tst.awk file1
hello;how|are you
{0:[]<>}{1:[hello;how]<|>}{2:[are you]<>}

$ awk -f tst.awk file2 hello how are_you {0:[]<>}{1:[hello]< >}{2:[how]< >}{3:[are_you]<>} $ awk -f tst.awk file3
    hello how are_you
{0:[]<    >}{1:[hello]< >}{2:[how]< >}{3:[are_you]<>}
3 RamanSailopal Jan 04 2021 at 16:51

Альтернативный вариант разделения - использовать совпадение для поиска разделителей полей и считывания их в массив:

awk -F'[;|]' '{
    str=$0; # Set str to the line while (match(str,FS)) { # Loop through rach match of the field separator map[cnt+=1]=substr(str,RSTART,RLENGTH); # Create an array of the field separators str=substr(str,RSTART+RLENGTH) # Set str to the rest of the string after the match string } for (i=1;i<=NF;i++) { printf "%s%s",$i,map[i] # Loop through each record, printing it along with the field separator held in the array map.
    } 
    printf "\n" 
   }' <<< "hello;how|are you"