POSIX sed ต้องการอะไรสำหรับ `1d; 1,2d 'ซึ่งช่วงที่อยู่เริ่มต้นจากบรรทัดที่ถูกลบไปแล้ว?


11

ในความคิดเห็นต่อคำถามนี้มีกรณีเกิดขึ้นที่การใช้งาน sed หลาย ๆ ครั้งไม่เห็นด้วยกับโปรแกรมที่ค่อนข้างง่ายและเรา (หรืออย่างน้อยฉัน) ไม่สามารถระบุได้ว่าสเปคนั้นต้องการอะไร

ปัญหาคือพฤติกรรมของช่วงที่เริ่มต้นที่บรรทัดที่ถูกลบ:

1d;1,2d

ควรลบบรรทัด 2แม้ว่าจุดเริ่มต้นของช่วงจะถูกลบออกก่อนที่จะถึงคำสั่งนั้นหรือไม่ ความคาดหวังเริ่มต้นของฉันคือ "ไม่" สอดคล้องกับ BSD sed ในขณะที่ GNU sed พูดว่า "ใช่" และการตรวจสอบข้อความข้อกำหนดไม่ได้ช่วยแก้ปัญหาทั้งหมด

ที่ตรงกับความคาดหวังของฉัน (อย่างน้อย) MacOS และ Solaris sedและ sedBSD ไม่เห็นด้วยคือ (อย่างน้อย) GNU และ Busybox sedและผู้คนมากมายที่นี่ สองคนแรกได้รับการรับรองจาก SUS ขณะที่คนอื่น ๆ มีแนวโน้มที่จะแพร่หลายมากขึ้น พฤติกรรมใดที่ถูกต้อง?


ข้อความข้อกำหนดสำหรับช่วงสองที่อยู่พูดว่า:

sedยูทิลิตี้แล้วให้ใช้บังคับในลำดับคำสั่งทั้งหมดที่มีอยู่เลือกพื้นที่รูปแบบที่จนกว่าคำสั่งเริ่มต้นรอบถัดไปหรือลาออก

และ

คำสั่งแก้ไขที่มีสองที่อยู่จะต้องเลือกช่วงที่ครอบคลุมจากพื้นที่รูปแบบแรกที่ตรงกับที่อยู่แรกผ่านพื้นที่รูปแบบต่อไปที่ตรงกับที่สอง [... ] เริ่มต้นที่บรรทัดแรกตามช่วงที่เลือก sed จะค้นหาที่อยู่แรกอีกครั้ง หลังจากนั้นกระบวนการจะทำซ้ำ

เนื้อหาที่บรรทัดที่ 2 อยู่ ใน "ช่วงรวมจากช่องว่างรูปแบบแรกที่ตรงกับที่อยู่แรกผ่านช่องว่างรูปแบบถัดไปที่ตรงกับที่สอง" โดยไม่คำนึงว่าจุดเริ่มต้นถูกลบไปแล้วหรือไม่ ในทางกลับกันฉันคาดว่าคนแรกที่dจะไปสู่รอบต่อไปและไม่ให้โอกาสในการเริ่มต้น การใช้งานที่ได้รับการรับรอง UNIX ทำในสิ่งที่ฉันคาดหวัง แต่อาจไม่ใช่สิ่งที่ข้อกำหนดกำหนด

บางการทดลองทำตามตัวอย่าง แต่คำถามที่สำคัญคือสิ่งที่ควร sedทำเมื่อช่วงเริ่มต้นในบรรทัดลบ?


การทดลองและตัวอย่าง

การสาธิตปัญหาอย่างง่ายคือสิ่งนี้ซึ่งพิมพ์สำเนาของบรรทัดเพิ่มเติมแทนที่จะลบออก:

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

นี้จะให้sedมีสองเส้นของการป้อนข้อมูลและa bโปรแกรมทำสองสิ่ง:

  1. 1dลบบรรทัดแรกกับ dคำสั่งจะ

    ลบพื้นที่รูปแบบและเริ่มรอบถัดไป และ

  2. เลือกช่วงของบรรทัดจาก 1 ถึง 2 และพิมพ์ออกมาอย่างชัดเจนนอกเหนือจากการพิมพ์อัตโนมัติทุกบรรทัดที่ได้รับ บรรทัดที่รวมอยู่ในช่วงจึงควรปรากฏสองครั้ง

ความคาดหวังของฉันคือสิ่งนี้ควรจะพิมพ์

b

เฉพาะกับช่วงที่ไม่ได้ใช้เพราะ1,2ไม่เคยไปถึงในช่วงบรรทัดที่ 1 (เนื่องจากdข้ามไปยังรอบ / บรรทัดถัดไปแล้ว) ดังนั้นการรวมช่วงจึงไม่เริ่มขึ้นในขณะที่aถูกลบไปแล้ว Unix seds ที่สอดคล้องกันของ macOS และ Solaris 10 สร้างเอาต์พุตนี้เช่นเดียวกับที่ไม่ใช่ POSIX sedใน Solaris และ BSD sedโดยทั่วไป

ในทางกลับกัน GNU ยังคงพิมพ์

b
b

แสดงว่ามีการตีความช่วง สิ่งนี้เกิดขึ้นทั้งในโหมด POSIX และไม่ใช่ sed ของ Busybox มีพฤติกรรมเหมือนกัน (แต่ไม่เหมือนกันเสมอไปดังนั้นมันดูเหมือนจะไม่เป็นผลมาจากรหัสที่ใช้ร่วมกัน)

ทดลองเพิ่มเติมกับ

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'

พบว่าดูเหมือนว่าจะรักษาช่วงเริ่มต้นที่บรรทัดที่ถูกลบราวกับว่ามันเริ่มต้นในบรรทัดต่อไปนี้ มองเห็นได้เนื่องจาก/c/ไม่ตรงกับช่วงสิ้นสุด ใช้/b/ในการเริ่มต้นช่วงไม่ไม่2ประพฤติตัวเช่นเดียวกับ


ตัวอย่างการทำงานครั้งแรกที่ฉันใช้คือ

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

เป็นวิธีการลบทุกบรรทัดจนถึงการ/a/แข่งขันนัดแรกแม้ว่าจะอยู่ในบรรทัดแรก (สิ่งที่ GNU จะใช้0,/a/dสำหรับ - นี่เป็นการแสดงผลที่เข้ากันได้กับ POSIX)

มีคนแนะนำว่าสิ่งนี้ควรลบถึงการจับคู่ที่สองของ/a/หากการจับคู่บรรทัดแรก (หรือทั้งไฟล์ถ้าไม่มีการจับคู่ที่สอง) ซึ่งดูเหมือนว่าน่าเชื่อถือ - แต่อีกครั้ง GNU เท่านั้นที่ทำเช่นนั้น ทั้ง macOS sed และ Solaris's sed สร้าง

b
c
d
e

สำหรับสิ่งนั้นตามที่ฉันคาดไว้ (GNU sed สร้างเอาต์พุตว่างจากการลบช่วงที่ไม่ได้ระบุไว้ Busybox sed พิมพ์เพียงdและeซึ่งผิดอย่างชัดเจนไม่ว่าจะเกิดอะไรขึ้น) โดยทั่วไปฉันคิดว่าพวกเขาผ่านการทดสอบตามมาตรฐานการรับรองหมายความว่าพฤติกรรมของพวกเขาถูกต้อง แต่มีคนมากพอที่จะแนะนำอย่างอื่นว่าฉันไม่แน่ใจข้อความข้อกำหนดไม่น่าเชื่อถืออย่างสมบูรณ์และชุดทดสอบไม่สามารถ ครอบคลุมอย่างสมบูรณ์แบบ

เห็นได้ชัดว่ามันไม่สามารถพกพาได้จริง ๆ ที่จะเขียนโค้ดนั้นในวันนี้เนื่องจากความไม่สอดคล้องกัน แต่ในทางทฤษฎีแล้วมันควรจะเทียบเท่ากันทุกหนทุกแห่งด้วยความหมายหนึ่งหรืออื่น ๆ ฉันคิดว่านี่เป็นข้อผิดพลาด แต่ฉันไม่รู้ว่าการใช้งานแบบใดเพื่อรายงาน มุมมองของฉันในขณะนี้คือพฤติกรรมของ GNU และ Busybox sed ไม่สอดคล้องกับข้อกำหนด แต่ฉันเข้าใจผิดว่า

POSIX ต้องการอะไรที่นี่?


ในฐานะวิธีแก้ปัญหาชั่วคราวให้เขียนไปที่ไฟล์ temp และประมวลผลด้วย POSIX edโดยไม่ผ่านsedทั้งหมดหรือไม่
D. Ben Knoble

คำตอบ:


9

นั่นถูกยกขึ้นในรายชื่อผู้รับจดหมายของกลุ่ม Austin ในเดือนมีนาคม 2012 นี่คือข้อความสุดท้ายเกี่ยวกับเรื่องนั้น (โดย Geoff Clare ของ Austin Group (เนื้อหาที่ดูแล POSIX) ซึ่งเป็นผู้ที่หยิบยกปัญหาขึ้นมาตั้งแต่แรก) คัดลอกมาที่นี่จากอินเตอร์เฟส NNTP ของ 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

และนี่คือส่วนที่เกี่ยวข้องของข้อความที่เหลือ (โดยฉัน) ที่ Geoff อ้างถึง:

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.

ดังนั้น (ตาม Geoff) POSIX มีความชัดเจนว่าพฤติกรรมของ GNU นั้นไม่สอดคล้อง

และมันเป็นความจริงมันมีความสอดคล้องน้อยกว่า (เปรียบเทียบseq 10 | sed -n '1d;1,2p'กับseq 10 | sed -n '1d;/^1$/,2p') แม้ว่าคนที่ไม่ได้ตระหนักถึงวิธีการประมวลผลที่น่าประหลาดใจน้อยกว่า (แม้ในตอนแรกเจฟฟ์ยังพบพฤติกรรมที่สอดคล้อง"แปลก" )

ไม่มีใครใส่ใจรายงานว่าเป็นบั๊กของกลุ่ม GNU ฉันไม่แน่ใจว่าฉันมีคุณสมบัติเป็นข้อผิดพลาด ตัวเลือกที่ดีที่สุดอาจเป็นไปได้ว่าจะมีการอัปเดตข้อมูลจำเพาะ POSIX เพื่อให้ทั้งสองพฤติกรรมสามารถทำให้ชัดเจนว่าไม่มีใครเชื่อถือได้

แก้ไข ตอนนี้ฉันได้ดูการsedใช้งานดั้งเดิมใน Unix V7 จากช่วงปลายยุค 70 และดูเหมือนว่าพฤติกรรมสำหรับที่อยู่ตัวเลขไม่ได้มีจุดประสงค์หรืออย่างน้อยก็ไม่ได้คิดอย่างถี่ถ้วน

ด้วยการอ่านข้อมูลจำเพาะของเจฟฟ์ (และการตีความดั้งเดิมของฉันว่าทำไมมันเกิดขึ้น) ตรงกันข้ามใน:

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

บรรทัด 1, 2, 4 และ 5 ควรเป็นเอาต์พุตเนื่องจากในเวลานี้เป็นที่อยู่ปลายทางที่ไม่เคยพบโดย1,3pคำสั่ง ranged เช่นเดียวกับในseq 5 | sed -n '3d;/1/,/3/p'

แต่นั่นไม่ได้เกิดขึ้นในการใช้งานดั้งเดิมหรือการใช้งานอื่น ๆ ที่ฉันได้ลองใช้ (busybox sedส่งคืนบรรทัดที่ 1, 2 และ 4 ซึ่งมีลักษณะเหมือนข้อบกพร่อง)

ถ้าคุณดูที่รหัส v7 UNIXก็จะตรวจสอบกรณีที่จำนวนบรรทัดปัจจุบันเป็นมากขึ้นกว่า (ตัวเลข) อยู่ที่สิ้นสุดและได้รับการออกของช่วงนั้น ความจริงที่ว่ามันไม่ได้ทำเพื่อที่อยู่เริ่มต้นดูเหมือนการกำกับดูแลมากกว่าการออกแบบโดยเจตนา

สิ่งที่หมายถึงคือไม่มีการใช้งานที่สอดคล้องกับการตีความของ POSIX spec ในเรื่องนั้นในขณะนี้

พฤติกรรมที่สับสนอีกประการหนึ่งของการนำ GNU ไปใช้คือ:

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

ตั้งแต่บรรทัดที่ 2 ถูกข้ามไป2,/3/จะถูกป้อนตามบรรทัดที่ 3 (บรรทัดแรกที่มีหมายเลขคือ> = 2) แต่เนื่องจากเป็นบรรทัดที่ทำให้เราป้อนช่วงจึงไม่ได้ตรวจสอบที่อยู่ปลายทาง มันแย่ลงด้วยbusybox sedใน:

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

เนื่องจากลบบรรทัด 2 ถึง 7 ออกแล้วบรรทัด 8 จึงเป็นบรรทัดแรกที่> = 2 ดังนั้นจึงป้อนช่วง 2,3 !


1
ดังนั้นดูเหมือนว่าปัญหาจะยังไม่ได้รับการแก้ไข - ฉันเห็นด้วยกับเหตุผลของคุณว่าทำไมมันจึงเกิดขึ้น แต่ก็ยังไม่ชัดเจนว่าเป็นสิ่งที่ต้องการ - แม้ว่ามันจะดูเหมือน Geoff ก็เชื่อมั่นในข้อความที่ยกมาว่า ถูกต้อง นั่นคือการอ่านของคุณเช่นกัน?
Michael Homer

1
@MichaelHomer ความคิดคือ (ตาม Geoff) POSIX ชัดเจนว่าพฤติกรรมของ GNU นั้นไม่สอดคล้อง และมันเป็นความจริงมันมีความสอดคล้องน้อยกว่า (เปรียบเทียบseq 10 | sed -n '1d;1,2p'กับseq 10 | sed -n '1d;/^1$/,2p') แม้ว่าผู้คนจะประหลาดใจน้อยกว่า แต่ก็ไม่ได้ตระหนักถึงวิธีการประมวลผลช่วง ไม่มีใครใส่ใจที่รายงานว่าเป็นบั๊กของกลุ่ม GNU ฉันไม่แน่ใจว่าฉันมีคุณสมบัติเป็นข้อผิดพลาดอาจเป็นตัวเลือกที่ดีที่สุดที่จะอัปเดตข้อมูลจำเพาะ POSIX เพื่อให้ทั้งสองพฤติกรรมสามารถระบุได้ชัดเจนว่าไม่สามารถเชื่อถือได้
Stéphane Chazelas

2
ในความเป็นจริงเนื่องจากคำจำกัดความของ POSIX ไม่จำเป็นต้องมีคำว่า "เห็น" เพื่อเริ่มต้นหรือสิ้นสุดช่วงที่อยู่การใช้งาน IMO ของ GNU นั้นตามด้วยการใช้คำ POSIX อย่างเคร่งครัด (น่าแปลกใจสำหรับ GNU!) นี่เป็นพฤติกรรมที่ต้องการสำหรับกรณีในชีวิตจริงส่วนใหญ่ที่ฉันรู้จัก แต่ในขณะที่คุณชี้ให้เห็นว่ามันจะต้องมีความสอดคล้อง และการตรวจสอบแต่ละบรรทัดสำหรับรูปแบบช่วงแม้หลังจากdนั้นไม่เพียง แต่เป็นปัญหาด้านประสิทธิภาพ แต่ยังนำไปสู่ปัญหาการใช้งานเพิ่มเติมเนื่องจากรูปแบบ "ที่มองไม่เห็น" ที่จำเป็นสำหรับช่วงไม่ได้รับอนุญาตให้มีผลต่อรูปแบบที่ว่างเปล่าเพิ่มเติม ...
Philippos

@Philippos ใน1d;1,2pสคริปต์นั้น1,2pคำสั่งจะไม่ทำงานในบรรทัดแรกดังนั้นที่อยู่แรกจะไม่ถูกจับคู่ด้วยพื้นที่รูปแบบใด ๆซึ่งเป็นวิธีหนึ่งในการตีความข้อความนั้น ไม่ว่าในกรณีใดควรเห็นได้ชัดว่าควรทำการประเมินที่อยู่ ณ เวลาที่ทำการรันคำสั่ง sed 's/./x/g; /xxx/,/xxx/d'
กดไลค์

2
@Isaac นั่นคือแก่นของปัญหา ในภาษา POSIX 1และ/1/เป็นทั้งสองที่อยู่1เป็นที่อยู่เมื่อหมายเลขบรรทัดคือ 1 /1/คือที่อยู่เมื่อมีพื้นที่รูปแบบ1คำถามคือว่าที่อยู่ทั้งสองประเภทควรได้รับการปฏิบัติเหมือนกันหรือหากช่วงหมายเลขบรรทัดควรพิจารณา " ในที่สุด "โดยไม่คำนึงว่าพวกเขาตรงกันหรือไม่ ดูการแก้ไขล่าสุดของฉันสำหรับบริบททางประวัติศาสตร์เพิ่มเติม
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.