Có trường nào lưu trữ dấu tách trường chính xác FS được sử dụng khi ở trong một biểu thức chính quy, tương đương với RT cho RS không?
Trong Phần tách bản ghigawk 4.1.2 của GNU Awk, chúng ta có thể đọc:
Khi nào
RS
là một ký tự,RT
chứa cùng một ký tự. Tuy nhiên, khiRS
là một biểu thức chính quy,RT
chứa văn bản đầu vào thực tế khớp với biểu thức chính quy.
Biến RT
này rất hữu ích trong một số trường hợp .
Tương tự, chúng ta có thể đặt một biểu thức chính quy làm dấu phân tách trường. Ví dụ, ở đây chúng tôi cho phép nó là ";" hoặc "|":
$ 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
Tuy nhiên, nếu chúng ta muốn đóng gói dữ liệu lại, chúng ta không có cách nào để biết dấu phân cách nào đã xuất hiện giữa hai trường. Vì vậy, nếu trong ví dụ trước, tôi muốn lặp lại các trường và in chúng lại với nhau bằng cách sử dụng FS
, nó sẽ in toàn bộ biểu thức trong mọi trường hợp:
$ 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
Có cách nào để "đóng gói lại" các trường bằng cách sử dụng dấu tách trường cụ thể được sử dụng để tách từng trường trong số chúng, tương tự như những gì RT sẽ cho phép thực hiện không?
(các ví dụ được đưa ra trong câu hỏi khá đơn giản, nhưng chỉ để chỉ ra điểm chính)
Trả lời
Có cách nào để "đóng gói lại" các trường bằng cách sử dụng dấu phân tách trường cụ thể được sử dụng để tách từng trường trong số chúng không
Sử dụng gnu-awk
split()có tham số thứ 4 bổ sung cho dấu phân cách phù hợp bằng cách sử dụng regex được cung cấp:
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
Một phiên bản dễ đọc hơn:
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"
Hãy lưu ý đến seps
tham số thứ 4 trong split
đó lưu trữ một mảng văn bản được so khớp bằng biểu thức chính quy được sử dụng trong tham số thứ 3, tức là /[;|]/
.
Tất nhiên, nó không ngắn và đơn giản như RS
, ORS
và RT
, có thể được viết như sau:
awk -v RS='[;|]' '{ORS = RT} 1' <<< "$s"
Như @anubhava đề cập , gawk có split()
(và patsplit()
đó là FPAT
như split()
là FS
- xemhttps://www.gnu.org/software/gawk/manual/gawk.html#String-Functions) để làm những gì bạn muốn. Nếu bạn muốn có chức năng tương tự với awk POSIX thì:
$ 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)
}
}
Lưu ý cách xử lý cụ thể ở trên đối với trường hợp có dấu phân tách trường " "
vì điều đó có nghĩa là 2 điều khác với tất cả các giá trị dấu phân tách trường khác:
- Các trường thực sự được phân tách bằng các chuỗi của bất kỳ khoảng trắng nào và
- Khoảng trắng hàng đầu sẽ bị bỏ qua khi điền $ 1 (hoặc flds [1] trong trường hợp này) và do đó, khoảng trắng đó, nếu nó tồn tại, phải được ghi lại trong seps [0] `cho mục đích của chúng tôi vì mọi seps [N] đều được liên kết với flds [N] đứng trước nó.
Ví dụ: chạy ở trên trên 3 tệp đầu vào sau:
$ head file{1..3}
==> file1 <==
hello;how|are you
==> file2 <==
hello how are_you
==> file3 <==
hello how are_you
chúng ta sẽ nhận được kết quả đầu ra sau đây trong đó mỗi trường được hiển thị dưới dạng số trường sau đó là giá trị trường bên trong [...]
sau đó là dấu phân tách bên trong <...>
, tất cả bên trong {...}
(lưu ý rằng seps[0]
FS được điền IFF " "
và bản ghi bắt đầu bằng khoảng trắng):
$ 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]<>}
Một tùy chọn thay thế để tách là sử dụng đối sánh để tìm các dấu phân tách trường và đọc chúng thành một mảng:
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"