Chế độ nhếch nhác trong awk?


16

Công cụ muốn sed, awkhoặc perl -nxử lý một đầu vào của họ ghi lại tại một thời điểm, hồ sơdòng theo mặc định.

Một số, như awkvới RS, GNU sed-zhoặc perl-0ooothể thay đổi loại bản ghi bằng cách chọn một dấu tách bản ghi khác.

perl -ncó thể làm cho toàn bộ đầu vào (mỗi tệp riêng lẻ khi truyền một số tệp) thành một bản ghi với -0777tùy chọn (hoặc -0theo sau là bất kỳ số bát phân nào lớn hơn 0377, 777 là số chính tắc). Đó là những gì họ gọi là chế độ bùn .

Một cái gì đó tương tự có thể được thực hiện với awk's RShoặc bất kỳ cơ chế khác? Trường hợp awkxử lý từng nội dung tệp theo thứ tự trái ngược với từng dòng của mỗi tệp?

Câu trả lời:


15

Bạn có thể thực hiện các cách tiếp cận khác nhau tùy thuộc vào việc awkxử lý RSnhư một ký tự đơn lẻ (như cách awktriển khai truyền thống ) hoặc như một biểu thức thông thường (thích gawkhoặc mawklàm). Các tập tin trống cũng rất khó để được coi là awkcó xu hướng bỏ qua chúng.

gawk, mawkHoặc khác awktriển khai ở đâu RScó thể là một biểu thức chính quy.

Trong các triển khai đó (đối với mawk, hãy cẩn thận rằng một số HĐH như Debian gửi phiên bản rất cũ thay vì phiên bản hiện đại được duy trì bởi @ThomasDickey ), nếu RSchứa một ký tự, dấu tách bản ghi là ký tự đó hoặc awkvào chế độ đoạn khi RStrống, hoặc coi RSnhư một biểu thức thông thường khác.

Giải pháp là sử dụng một biểu thức chính quy không thể phù hợp. Một số đến với tâm trí như x^hoặc $x( xtrước khi bắt đầu, hoặc sau khi kết thúc). Tuy nhiên, một số (đặc biệt với gawk) đắt hơn những cái khác. Cho đến nay, tôi đã thấy rằng đó ^$là một cách hiệu quả nhất. Nó chỉ có thể khớp với đầu vào trống, nhưng sau đó sẽ không có gì phù hợp.

Vì vậy, chúng ta có thể làm:

awk -v RS='^$' '{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

Một điều lưu ý là nó bỏ qua các tập tin trống (trái với perl -0777 -n). Điều đó có thể được giải quyết với GNU awkbằng cách đặt mã trong một ENDFILEcâu lệnh thay thế. Nhưng chúng ta cũng cần đặt lại $0trong câu lệnh BEGINFILE vì nếu không nó sẽ không được đặt lại sau khi xử lý tệp trống:

gawk -v RS='^$' '
   BEGINFILE{$0 = ""}
   ENDFILE{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

awktriển khai truyền thống , POSIXawk

Trong đó, RSchỉ là một ký tự, họ không có BEGINFILE/ ENDFILE, họ không có RTbiến, họ thường không thể xử lý ký tự NUL.

Bạn sẽ nghĩ rằng việc sử dụng RS='\0'có thể hoạt động sau đó vì dù sao họ không thể xử lý đầu vào có chứa byte NUL, nhưng không, RS='\0'trong các triển khai truyền thống được coi RS=là chế độ đoạn văn.

Một giải pháp có thể là sử dụng một ký tự không chắc chắn được tìm thấy trong đầu vào như thế nào \1. Trong các địa điểm ký tự đa nhân, bạn thậm chí có thể tạo ra các chuỗi byte rất khó xảy ra khi chúng tạo thành các ký tự không được gán hoặc không phải ký tự như $'\U10FFFE'trong các địa phương UTF-8. Mặc dù không thực sự hoàn hảo và bạn cũng gặp vấn đề với các tập tin trống.

Một giải pháp khác có thể là lưu trữ toàn bộ đầu vào trong một biến và xử lý nó trong câu lệnh END ở cuối. Điều đó có nghĩa là bạn chỉ có thể xử lý một tệp tại một thời điểm:

awk '{content = content $0 RS}
     END{$0 = content
       printf "%s: <%s>\n", FILENAME, $0
     }' file

Đó là tương đương với sed:

sed '
  :1
  $!{
   N;b1
  }
  ...' file1

Một vấn đề khác với cách tiếp cận đó là nếu tệp không kết thúc bằng ký tự dòng mới (và không trống), $0thì cuối cùng vẫn được thêm vào một cách tùy tiện (với gawk, bạn sẽ xử lý xung quanh bằng cách sử dụng RTthay vì RStrong mã ở trên). Một lợi thế là bạn có bản ghi số lượng dòng trong tệp trong NR/ FNR.


đối với phần cuối cùng ("nếu tệp không kết thúc bằng ký tự dòng mới (và không trống), một phần vẫn được thêm tùy ý vào $ 0 ở cuối"): đối với tệp văn bản, chúng được cho là có phần kết thúc dòng mới. vi thêm một ví dụ, và do đó sửa đổi tệp khi bạn lưu nó. Không có dòng mới kết thúc sẽ khiến một số lệnh loại bỏ "dòng" cuối cùng (ví dụ: wc) nhưng những dòng khác vẫn 'nhìn thấy' dòng cuối cùng ... ymmv. Do đó, giải pháp của bạn là hợp lệ, imo, nếu bạn phải xử lý các tệp văn bản (có lẽ là trường hợp này, vì awk tốt cho xử lý văn bản nhưng không tốt cho nhị phân ^^)
Olivier Dulac

1
cố gắng nhét tất cả vào có thể gặp một số hạn chế ... awk truyền thống rõ ràng có (có?) giới hạn 99 trường trên một dòng ... vì vậy bạn có thể cần sử dụng một FS khác để tránh giới hạn đó, nhưng bạn có thể cũng có giới hạn về tổng chiều dài của một dòng (hoặc toàn bộ, nếu bạn quản lý để có được tất cả trên một dòng) có thể là bao nhiêu?
Olivier Dulac

cuối cùng: một hack (ngớ ngẩn ...) có thể là phân tích cú pháp đầu tiên toàn bộ tệp và tìm kiếm một char không có trong đó, sau đó tr '\n' 'thatchar' tệp trước khi gửi nó đến awk, và tr 'thatchar' \n'đầu ra? (bạn có thể vẫn cần nối thêm một dòng mới để đảm bảo, như tôi đã lưu ý ở trên, tệp đầu vào của bạn có một dòng mới kết thúc: { tr '\n' 'missingchar' < thefile ; printf "\n" ;} | awk ..... | { tr 'missingchar' '\n' }(nhưng cuối cùng lại thêm một '\ n', rằng bạn có thể cần phải thoát khỏi ... có thể thêm một sed trước tr cuối cùng? nếu tr đó chấp nhận các tập tin mà không chấm dứt các dòng mới ...)
Olivier Dulac

@OlivierDulac, giới hạn về số lượng trường sẽ chỉ bị tấn công nếu chúng tôi đang truy cập vào NF hoặc bất kỳ trường nào. awkkhông thực hiện việc chia tách nếu chúng ta không. Phải nói rằng, ngay cả /bin/awkSolaris 9 (dựa trên awk của năm 1970) cũng có giới hạn đó, vì vậy tôi không chắc chúng ta có thể tìm thấy cái nào (vẫn có thể là chim ưng của SVR4 có giới hạn 99 và nawk 199, vì vậy nó có khả năng việc nâng giới hạn đó đã được Sun thêm vào và có thể không được tìm thấy trong các aws dựa trên SVR4 khác, bạn có thể thử nghiệm trên AIX không?).
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.