POSIX sed yêu cầu gì cho `1d; 1,2d` trong đó phạm vi địa chỉ bắt đầu từ một dòng đã bị xóa?


11

Trong các bình luận cho câu hỏi này, một trường hợp đã được đưa ra trong đó các triển khai sed khác nhau không đồng ý với một chương trình khá đơn giản và chúng tôi (hoặc ít nhất là tôi) không thể xác định được đặc tả thực sự cần gì cho nó.

Vấn đề là hành vi của một phạm vi bắt đầu tại một dòng bị xóa:

1d;1,2d

Có nên xóa dòng 2 ngay cả khi bắt đầu phạm vi bị xóa trước khi đến lệnh đó không? Kỳ vọng ban đầu của tôi là "không" phù hợp với BSD sed, trong khi GNU sed nói "có" và việc kiểm tra văn bản đặc tả không giải quyết được hoàn toàn vấn đề.

Phù hợp với kỳ vọng của tôi là (ít nhất) macOS và Solaris sedvà BSD sed. Không đồng ý là (ít nhất) GNU và Busybox sed, và nhiều người ở đây. Hai cái đầu tiên được chứng nhận SUS trong khi những cái khác có khả năng phổ biến rộng rãi hơn. Hành vi nào là đúng?


Các văn bản đặc điểm kỹ thuật cho các phạm vi hai địa chỉ nói:

Sau đó, tiện ích sed sẽ áp dụng theo thứ tự tất cả các lệnh có địa chỉ chọn không gian mẫu đó, cho đến khi lệnh bắt đầu chu trình tiếp theo hoặc thoát.

Lệnh chỉnh sửa có hai địa chỉ sẽ chọn phạm vi bao gồm từ không gian mẫu đầu tiên khớp với địa chỉ đầu tiên thông qua không gian mẫu tiếp theo khớp với địa chỉ thứ hai. [...] Bắt đầu từ dòng đầu tiên theo phạm vi đã chọn, sed sẽ tìm lại địa chỉ đầu tiên. Sau đó, quá trình sẽ được lặp lại.

Có thể cho rằng, dòng 2 trong "phạm vi bao gồm từ không gian mô hình đầu tiên mà phù hợp với địa chỉ đầu tiên thông qua không gian mô hình tiếp theo phù hợp với thứ hai", bất kể điểm khởi đầu đã bị xóa. Mặt khác, tôi dự kiến ​​người đầu tiên dsẽ chuyển sang chu kỳ tiếp theo và không cho phạm vi bắt đầu. Các triển khai được chứng nhận UNIX ™ thực hiện những gì tôi mong đợi, nhưng có khả năng không phải là những gì đặc tả bắt buộc.

Một số thí nghiệm minh họa theo, nhưng câu hỏi chính là: những gì nên sed làm gì khi một loạt bắt đầu trên một dòng bị xóa?


Thí nghiệm và ví dụ

Một minh chứng đơn giản cho vấn đề này là vấn đề này in ra các bản sao thêm của các dòng thay vì xóa chúng:

printf 'a\nb\n' | sed -e '1d;1,2p'

Điều này cung cấp sedhai dòng đầu vào ab. Chương trình thực hiện hai điều:

  1. Xóa dòng đầu tiên với 1d. Các dlệnh sẽ

    Xóa không gian mẫu và bắt đầu chu kỳ tiếp theo. và

  2. Chọn phạm vi của các dòng từ 1 đến 2 và in rõ ràng chúng ra, ngoài việc in tự động mỗi dòng nhận được. Do đó, một dòng trong phạm vi sẽ xuất hiện hai lần.

Kỳ vọng của tôi là điều này sẽ in

b

chỉ, với phạm vi không áp dụng vì 1,2không bao giờ đạt được trong dòng 1 (vì dđã nhảy sang chu kỳ / dòng tiếp theo) và do đó, phạm vi bao gồm không bao giờ bắt đầu, trong khi ađã bị xóa. Các Unix phù hợp sedcủa macOS và Solaris 10 tạo ra đầu ra này, cũng như không phải POSIX sedtrong Solaris và BSD sednói chung.

GNU sed, mặt khác, in

b
b

chỉ ra rằng nó đã giải thích phạm vi. Điều này xảy ra cả trong chế độ POSIX và không. Sedy của Busybox có hành vi tương tự (nhưng không phải hành vi giống hệt nhau, vì vậy dường như đó không phải là kết quả của mã được chia sẻ).

Thử nghiệm thêm với

printf 'a\nb\nc\nd\ne\n' | sed -e '2d;2,/c/p'
printf 'a\nb\nc\nd\ne\n' | sed -e '2d;2,/d/p'

thấy rằng nó xuất hiện để xử lý một phạm vi bắt đầu từ một dòng bị xóa như thể nó bắt đầu trên dòng sau . Điều này có thể nhìn thấy vì /c/không khớp để kết thúc phạm vi. Sử dụng /b/để bắt đầu phạm vi không hoạt động giống như 2.


Ví dụ làm việc ban đầu tôi đang sử dụng là

printf '%s\n' a b c d e | sed -e '1{/a/d;};1,//d'

như một cách để xóa tất cả các dòng cho đến /a/khớp đầu tiên , ngay cả khi đó là trên dòng đầu tiên (thứ mà GNU sed sẽ sử dụng 0,/a/dcho - đây là một biểu hiện tương thích POSIX đã cố gắng).

Có ý kiến ​​cho rằng điều này thay vào đó nên xóa tối đa trận đấu thứ hai/a/ nếu dòng đầu tiên khớp (hoặc toàn bộ tệp nếu không có kết quả khớp thứ hai), điều này có vẻ hợp lý - nhưng một lần nữa, chỉ GNU sed mới làm điều đó. Cả hai sản phẩm sed của macOS và Solaris

b
c
d
e

vì điều đó, như tôi mong đợi (GNU sed tạo ra đầu ra trống để loại bỏ phạm vi bị hủy bỏ; Busybox sed chỉ in de, rõ ràng là không có vấn đề gì). Nói chung, tôi cho rằng họ đã vượt qua các bài kiểm tra tuân thủ chứng nhận có nghĩa là hành vi của họ là đúng, nhưng đủ người đã đề nghị khác là tôi không chắc chắn, văn bản đặc tả không hoàn toàn thuyết phục và bộ kiểm tra không thể hoàn toàn toàn diện.

Rõ ràng ngày nay nó không thực sự di động để viết mã đó với sự không nhất quán, nhưng về mặt lý thuyết nó phải tương đương ở mọi nơi với nghĩa này hay nghĩa khác. Tôi nghĩ rằng đây là một lỗi, nhưng tôi không biết phải báo cáo việc triển khai nào. Quan điểm của tôi hiện tại là hành vi của GNU và Busybox sed không phù hợp với đặc điểm kỹ thuật, nhưng tôi có thể bị nhầm lẫn về điều đó.

POSIX yêu cầu gì ở đây?


Như một giải pháp tạm thời, ghi vào tệp tạm thời và xử lý nó với POSIX ed, bỏ qua sedhoàn toàn?
D. Ben Knoble

Câu trả lời:


9

Nó đã được đưa ra trong danh sách gửi thư của nhóm Austin vào tháng 3 năm 2012. Đây là thông điệp cuối cùng về điều đó (bởi Geoff Clare thuộc Tập đoàn Austin (cơ quan duy trì POSIX), người cũng là người đưa ra vấn đề ngay từ đầu). Ở đây được sao chép từ giao diện NNTP của gmane:

Date: Fri, 16 Mar 2012 17:09:42 +0000
From: Geoff Clare <gwc-7882/jkIBncuagvECLh61g@public.gmane.org>
To: austin-group-l-7882/jkIBncuagvECLh61g@public.gmane.org
Newsgroups: gmane.comp.standards.posix.austin.general
Subject: Re: Strange addressing issue in sed

Stephane Chazelas <stephane_chazelas-Qt13gs6zZMY@public.gmane.org> wrote, on 16 Mar 2012:
>
> 2012-03-16 15:44:35 +0000, Geoff Clare:
> > I've been alerted to an odd behaviour of sed on certified UNIX
> > systems that doesn't seem to match the requirements of the
> > standard.  It concerns an interaction between the 'n' command
> > and address matching.
> > 
> > According to the standard, this command:
> > 
> > printf 'A\nB\nC\nD\n' | sed '1,3s/A/B/;1,3n;1,3s/B/C/'
> > 
> > should produce the output:
> > 
> > B
> > C
> > C
> > D
> > 
> > GNU sed does produce this, but certified UNIX systems produce this:
> > 
> > B
> > B
> > C
> > D
> > 
> > However, if I change the 1,3s/B/C/ to 2,3s/B/C/ then they produce
> > the expected output (tested on Solaris and HP-UX).
> > 
> > Is this just an obscure bug from common ancestor code, or is there
> > some legitimate reason why this address change alters the behaviour?
> [...]
> 
> I suppose the idea is that for the second 1,3cmd, line "1" has
> not been seen, so the 1,3 range is not entered.

Ah yes, now it makes sense, and it looks like the standard does
require this slightly strange behaviour, given how the processing
of the "two addresses" case is specified:

    An editing command with two addresses shall select the inclusive
    range from the first pattern space that matches the first address
    through the next pattern space that matches the second.  (If the
    second address is a number less than or equal to the line number
    first selected, only one line shall be selected.) Starting at the
    first line following the selected range, sed shall look again for
    the first address. Thereafter, the process shall be repeated.

It's specified this way because the addresses can be BREs, but if
the same matching process is applied to the line numbers (even though
they can only match at most once), then the 1,3 range on that last
command is never entered.

-- 
Geoff Clare <g.clare-7882/jkIBncuagvECLh61g@public.gmane.org>
The Open Group, Apex Plaza, Forbury Road, Reading, RG1 1AX, England

Và đây là phần có liên quan của phần còn lại của tin nhắn (bởi tôi) mà Geoff đã trích dẫn:

I suppose the idea is that for the second 1,3cmd, line "1" has
not been seen, so the 1,3 range is not entered.

Same idea as in

printf '%s\n' A B C | sed -n '1d;1,2p'

whose behavior differ in traditional (heirloom toolchest at
least) and GNU.

It's unclear to me whether POSIX wants one behavior or the
other.

Vì vậy, (theo Geoff) POSIX rõ ràng rằng hành vi GNU không tuân thủ.

Và đúng là nó ít nhất quán (so sánh seq 10 | sed -n '1d;1,2p'với seq 10 | sed -n '1d;/^1$/,2p') ngay cả khi có khả năng ít gây ngạc nhiên hơn cho những người không nhận ra cách xử lý phạm vi (ngay cả Geoff ban đầu cũng thấy hành vi tuân thủ "lạ" ).

Không ai bận tâm báo cáo nó là một lỗi cho những người GNU. Tôi không chắc chắn tôi đủ điều kiện nó là một lỗi. Có lẽ tùy chọn tốt nhất sẽ là thông số kỹ thuật POSIX được cập nhật để cho phép cả hai hành vi làm rõ rằng người ta không thể dựa vào một trong hai.

Chỉnh sửa . Bây giờ tôi đã xem xét sedtriển khai ban đầu trong Unix V7 từ cuối những năm 70 và có vẻ như hành vi đó đối với các địa chỉ số không được dự định hoặc ít nhất là không được nghĩ đến hoàn toàn ở đó.

Với việc đọc thông số kỹ thuật của Geoff (và cách giải thích ban đầu của tôi về lý do tại sao nó xảy ra), ngược lại, trong:

seq 5 | sed -n '3d;1,3p'

các dòng 1, 2, 4 và 5 phải là đầu ra, vì lần này, đó là địa chỉ kết thúc không bao giờ gặp phải bởi 1,3plệnh ranged, như trongseq 5 | sed -n '3d;/1/,/3/p'

Tuy nhiên, điều đó không xảy ra trong triển khai ban đầu, cũng như bất kỳ triển khai nào tôi đã thử (busybox sedtrả về các dòng 1, 2 và 4 trông giống như một lỗi).

Nếu bạn nhìn vào mã UNIX v7 , nó sẽ kiểm tra trường hợp số dòng hiện tại lớn hơn địa chỉ kết thúc (số) và sau đó ra khỏi phạm vi. Thực tế là nó không làm điều đó cho địa chỉ bắt đầu trông giống như một sự giám sát hơn là một thiết kế có chủ ý.

Điều đó có nghĩa là không có triển khai nào thực sự phù hợp với cách giải thích của thông số POSIX về vấn đề đó vào lúc này.

Một hành vi khó hiểu khác với việc triển khai GNU là:

$ seq 5 | sed -n '2d;2,/3/p'
3
4
5

Vì dòng 2 đã bị bỏ qua, dòng 2,/3/được nhập vào dòng 3 (dòng đầu tiên có số> = 2). Nhưng vì đó là dòng khiến chúng tôi vào phạm vi, nó không được kiểm tra địa chỉ cuối . Nó trở nên tồi tệ hơn với busybox sed:

$ seq 10 | busybox sed -n '2,7d; 2,3p'
8

Vì dòng 2 đến 7 đã bị xóa, dòng 8 là dòng đầu tiên> = 2 nên phạm vi 2,3 được nhập vào sau đó!


1
Vì vậy, có vẻ như vấn đề vẫn chưa được giải quyết - Tôi đồng ý với lý do của bạn về lý do tại sao nó xảy ra, nhưng cũng không rõ liệu đó có phải là điều mong muốn hay không - mặc dù nghe có vẻ như Geoff đã bị thuyết phục bởi văn bản trích dẫn mà UNIX triển khai đã đúng Đó có phải là đọc của bạn không?
Michael Homer

1
@MichaelHomer, ý tưởng là (theo Geoff) POSIX rõ ràng rằng hành vi GNU không tuân thủ. Và đúng là nó ít nhất quán (so sánh seq 10 | sed -n '1d;1,2p'với seq 10 | sed -n '1d;/^1$/,2p') ngay cả khi mọi người ít ngạc nhiên hơn sẽ không nhận ra cách xử lý phạm vi. Không ai bận tâm báo cáo nó là một lỗi cho những người GNU. Tôi không chắc chắn rằng tôi đủ điều kiện là lỗi, có lẽ tùy chọn tốt nhất là cập nhật thông số POSIX để cho phép cả hai hành vi làm rõ rằng người ta không thể dựa vào một trong hai.
Stéphane Chazelas

2
Trên thực tế, vì định nghĩa POSIX không đưa ra tuyên bố rằng các địa chỉ cần được "nhìn thấy" để bắt đầu hoặc kết thúc một phạm vi địa chỉ, IMO thực hiện GNU tuân theo cách diễn đạt POSIX nghiêm ngặt hơn (đáng ngạc nhiên đối với GNU!). Đây cũng là hành vi mong muốn cho hầu hết các trường hợp thực tế mà tôi biết. Nhưng, như bạn chỉ ra, nó sẽ cần phải nhất quán. Và việc kiểm tra từng dòng cho các mẫu phạm vi ngay cả sau đó dkhông chỉ là vấn đề về hiệu năng, nó dẫn đến các vấn đề triển khai tiếp theo vì các mẫu "không nhìn thấy" cần thiết cho các phạm vi không được phép có hiệu lực đối với các mẫu trống tiếp theo ... một mớ hỗn độn!
Philippos

@Philippos, trong 1d;1,2ptập 1,2plệnh đó, lệnh không được chạy trên dòng đầu tiên, vì vậy địa chỉ đầu tiên không được khớp với bất kỳ không gian mẫu nào , đây là một cách để diễn giải văn bản đó. Trong mọi trường hợp, rõ ràng là việc đánh giá các địa chỉ nên được thực hiện tại thời điểm lệnh được chạy. Thích trongsed 's/./x/g; /xxx/,/xxx/d'
Stéphane Chazelas

2
@Isaac, đó là cốt lõi của vấn đề. Trong ngôn ngữ POSIX 1/1/là cả hai địa chỉ, 1là địa chỉ khi số dòng là 1, /1/là địa chỉ khi không gian mẫu chứa 1, câu hỏi là liệu cả hai loại địa chỉ nên được xử lý như nhau hay nếu phạm vi số dòng nên xem xét " trong tuyệt đối "bất kể họ có khớp hay không. Xem thêm chỉnh sửa mới nhất của tôi cho bối cảnh lịch sử hơn.
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.