Есть ли поле, в котором хранится точный разделитель полей FS, используемый в регулярном выражении, эквивалентный RT для RS?
В 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?
(примеры, приведенные в вопросе, довольно просты, но просто для того, чтобы показать суть)
Ответы
Есть ли способ «переупаковать» поля, используя специальный разделитель полей, используемый для разделения каждого из них
Использование 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"
Как упоминает @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 (или 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]<>}
Альтернативный вариант разделения - использовать совпадение для поиска разделителей полей и считывания их в массив:
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"