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?

Jan 04 2021

Trong Phần tách bản ghigawk 4.1.2 của GNU Awk, chúng ta có thể đọc:

Khi nào RSlà một ký tự, RTchứa cùng một ký tự. Tuy nhiên, khi RSlà một biểu thức chính quy, RTchứa văn bản đầu vào thực tế khớp với biểu thức chính quy.

Biến RTnà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

8 anubhava Jan 04 2021 at 16:34

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 sepstham 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, ORSRT, có thể được viết như sau:

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

Như @anubhava đề cập , gawk có split()(và patsplit()đó là FPATnhư split()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:

  1. 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à
  2. 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]<>}
3 RamanSailopal Jan 04 2021 at 16:51

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"