Esiste un campo che memorizza il separatore di campo esatto FS utilizzato in un'espressione regolare, equivalente a RT per RS?
In GNU Awk 4.1.2 Record Splitting withgawk possiamo leggere:
Quando
RS
è un singolo carattere,RT
contiene lo stesso singolo carattere. Tuttavia, quandoRS
è un'espressione regolare,RT
contiene il testo di input effettivo che corrisponde all'espressione regolare.
Questa variabile RT
è molto utile in alcuni casi .
Allo stesso modo, possiamo impostare un'espressione regolare come separatore di campo. Ad esempio, qui permettiamo che sia ";" o "|":
$ 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
Tuttavia, se vogliamo impacchettare nuovamente i dati, non abbiamo un modo per sapere quale separatore è apparso tra due campi. Quindi, se nell'esempio precedente voglio scorrere i campi e stamparli di nuovo insieme usando FS
, stampa l'intera espressione in ogni caso:
$ 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
Esiste un modo per "reimballare" i campi utilizzando il separatore di campo specifico utilizzato per dividere ciascuno di essi, analogamente a ciò che RT consentirebbe di fare?
(gli esempi forniti nella domanda sono piuttosto semplici, ma solo per mostrare il punto)
Risposte
C'è un modo per "reimballare" i campi utilizzando il separatore di campo specifico utilizzato per dividere ciascuno di essi
L'utilizzo di gnu-awk
split()questo ha un quarto parametro aggiuntivo per il delimitatore corrispondente utilizzando l'espressione regolare fornita:
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
Una versione più leggibile:
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"
Prendi nota del quarto seps
parametro in split
quanto memorizza un array di testo abbinato tramite l'espressione regolare utilizzata nel terzo parametro, ad es /[;|]/
.
Naturalmente non è così breve e semplice come RS
, ORS
e RT
, che può essere scritto come:
awk -v RS='[;|]' '{ORS = RT} 1' <<< "$s"
Come menziona @anubhava , gawk ha split()
(e patsplit()
che è FPAT
come split()
è FS
- vedihttps://www.gnu.org/software/gawk/manual/gawk.html#String-Functions) per fare quello che vuoi. Se desideri la stessa funzionalità con un awk POSIX, allora:
$ 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)
}
}
Nota la gestione specifica sopra del caso in cui il separatore di campo è " "
perché ciò significa 2 cose diverse da tutti gli altri valori di separatore di campo:
- I campi sono effettivamente separati da catene di qualsiasi spazio bianco e
- Lo spazio bianco iniziale deve essere ignorato quando si popola $ 1 (o flds [1] in questo caso) e quindi lo spazio bianco, se esiste, deve essere catturato in seps [0] `per i nostri scopi poiché ogni seps [N] è associato con il flds [N] che lo precede.
Ad esempio, eseguendo quanto sopra su questi 3 file di input:
$ head file{1..3}
==> file1 <==
hello;how|are you
==> file2 <==
hello how are_you
==> file3 <==
hello how are_you
avremmo il seguente output in cui ogni campo viene visualizzato come il numero del campo, quindi il valore del campo all'interno, [...]
quindi il separatore all'interno <...>
, tutto all'interno {...}
(nota che seps[0]
è popolato IFF, FS è " "
e il record inizia con uno spazio bianco):
$ 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]<>}
Un'opzione alternativa per dividere è usare match per trovare i separatori di campo e leggerli in un array:
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"