Apa yang diperlukan POSIX untuk `1d; 1,2d` di mana rentang alamat dimulai dari baris yang sudah dihapus?

11

Dalam komentar untuk pertanyaan ini muncul kasus di mana berbagai implementasi sed tidak setuju pada program yang cukup sederhana, dan kami (atau setidaknya saya) tidak dapat menentukan spesifikasi yang sebenarnya diperlukan untuk itu.

Masalahnya adalah perilaku rentang yang dimulai pada baris yang dihapus:

1d;1,2d

Haruskah baris 2 dihapus meskipun awal rentang telah dihapus sebelum mencapai perintah itu? Harapan awal saya adalah "tidak" sejalan dengan sed BSD, sementara GNU sed mengatakan "ya", dan memeriksa teks spesifikasi tidak sepenuhnya menyelesaikan masalah.

Sesuai harapan saya adalah (setidaknya) macOS dan Solaris sed, dan BSD sed. Yang tidak setuju adalah (setidaknya) GNU dan Busybox sed, dan banyak orang di sini. Dua yang pertama bersertifikat SUS sementara yang lain lebih luas. Perilaku mana yang benar?


The spesifikasi teks untuk rentang dua alamat mengatakan:

The sed utilitas kemudian berlaku secara berurutan semua perintah yang alamat pilih ruang yang pola, sampai perintah mulai siklus berikutnya atau berhenti.

dan

Perintah pengeditan dengan dua alamat harus memilih rentang inklusif dari ruang pola pertama yang cocok dengan alamat pertama melalui ruang pola berikutnya yang cocok dengan yang kedua. [...] Mulai dari baris pertama mengikuti rentang yang dipilih, sed akan mencari lagi untuk alamat pertama. Setelah itu, proses harus diulang.

Dapat diperdebatkan, baris 2 berada dalam "rentang inklusif dari ruang pola pertama yang cocok dengan alamat pertama melalui ruang pola berikutnya yang cocok dengan yang kedua", terlepas dari apakah titik awal telah dihapus. Di sisi lain, saya mengharapkan yang pertama duntuk melanjutkan ke siklus berikutnya dan tidak memberikan rentang kesempatan untuk memulai. Implementasi bersertifikasi UNIX ™ melakukan apa yang saya harapkan, tetapi berpotensi tidak sesuai dengan mandat spesifikasi.

Beberapa eksperimen ilustratif mengikuti, tetapi pertanyaan kuncinya adalah: apa yang harus sed dilakukan ketika rentang dimulai pada baris yang dihapus?


Eksperimen dan contoh

Demonstrasi yang disederhanakan dari masalah ini adalah ini, yang mencetak salinan garis lebih banyak daripada menghapusnya:

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

Ini menyediakan seddua jalur input, adan b. Program ini melakukan dua hal:

  1. Menghapus baris pertama dengan 1d. The dperintah akan

    Hapus ruang pola dan mulai siklus berikutnya. dan

  2. Pilih rentang garis dari 1 hingga 2 dan cetak secara eksplisit, selain pencetakan otomatis yang diterima setiap baris. Garis yang termasuk dalam rentang dengan demikian akan muncul dua kali.

Harapan saya adalah ini harus dicetak

b

hanya, dengan rentang yang tidak berlaku karena 1,2tidak pernah tercapai selama baris 1 (karena sudah dmelompat ke siklus / baris berikutnya) dan dengan demikian jangkauan inklusi tidak pernah dimulai, sementara atelah dihapus. Unix seds dari macOS dan Solaris 10 menghasilkan output ini, seperti halnya non-POSIX seddi Solaris dan BSD sedsecara umum.

GNU sed, di sisi lain, mencetak

b
b

menunjukkan bahwa ia telah menafsirkan kisaran. Ini terjadi baik dalam mode POSIX dan tidak. Busybox's sed memiliki perilaku yang sama (tetapi perilaku yang tidak selalu identik, sehingga sepertinya bukan hasil dari kode bersama).

Eksperimen lebih lanjut dengan

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'

menemukan bahwa itu tampaknya memperlakukan rentang yang dimulai pada baris yang dihapus seolah-olah itu dimulai pada baris berikut . Ini terlihat karena /c/tidak cocok untuk mengakhiri rentang. Menggunakan /b/untuk memulai rentang tidak berperilaku sama dengan 2.


Contoh kerja awal yang saya gunakan adalah

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

sebagai cara untuk menghapus semua baris hingga /a/pertandingan pertama , bahkan jika itu ada di baris pertama (apa yang akan digunakan oleh GNU 0,/a/d- ini adalah percobaan yang kompatibel dengan POSIX untuk itu).

Disarankan bahwa ini seharusnya menghapus hingga kecocokan kedua/a/ jika baris pertama cocok (atau seluruh file jika tidak ada kecocokan kedua), yang tampaknya masuk akal - tetapi sekali lagi, hanya sed GNU yang melakukan itu. Baik sed macOS dan sed solaris

b
c
d
e

untuk itu, seperti yang saya perkirakan (GNU sed menghasilkan output kosong dari menghapus rentang yang tidak ditentukan; sed Busybox mencetak hanya ddan e, yang jelas salah apa pun). Secara umum saya akan berasumsi bahwa mereka telah lulus tes kesesuaian sertifikasi berarti bahwa perilaku mereka benar, tetapi cukup banyak orang menyarankan sebaliknya bahwa saya tidak yakin, teks spesifikasi tidak sepenuhnya meyakinkan, dan test suite tidak dapat sangat komprehensif.

Jelas itu tidak praktis portabel untuk menulis kode hari ini mengingat inkonsistensi, tetapi secara teoritis harus setara di mana-mana dengan satu makna atau yang lain. Saya pikir ini adalah bug, tapi saya tidak tahu implementasi mana yang harus dilaporkan. Pandangan saya saat ini adalah bahwa perilaku sed GNU dan Busybox tidak konsisten dengan spesifikasi, tapi saya bisa salah tentang itu.

Apa yang dibutuhkan POSIX di sini?

Michael Homer
sumber
Sebagai solusi sementara, menulis ke file temp dan memprosesnya dengan POSIX ed, melewati sedsemuanya?
D. Ben Knoble

Jawaban:

9

Itu diangkat di milis grup Austin pada Maret 2012. Inilah pesan terakhirnya (oleh Geoff Clare dari Austin Group (badan yang mengelola POSIX), yang juga merupakan orang yang mengangkat masalah ini sejak awal). Di sini disalin dari antarmuka NNTP gmane:

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

Stephane Chazelas <[email protected]> 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/[email protected]>
The Open Group, Apex Plaza, Forbury Road, Reading, RG1 1AX, England

Dan inilah bagian yang relevan dari sisa pesan (oleh saya) yang dikutip 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.

Jadi, (menurut Geoff) POSIX jelas bahwa perilaku GNU tidak sesuai.

Dan memang benar itu kurang konsisten (dibandingkan seq 10 | sed -n '1d;1,2p'dengan seq 10 | sed -n '1d;/^1$/,2p') bahkan jika berpotensi kurang mengejutkan bagi orang-orang yang tidak menyadari bagaimana rentang diproses (bahkan Geoff awalnya menemukan perilaku yang sesuai "aneh" ).

Tidak ada yang peduli melaporkannya sebagai bug ke orang-orang GNU. Saya tidak yakin saya akan memenuhi syarat sebagai bug. Mungkin opsi terbaik adalah untuk memperbarui spesifikasi POSIX untuk memungkinkan kedua perilaku untuk memperjelas bahwa seseorang tidak dapat mengandalkan keduanya.

Edit . Saya sekarang telah melihat sedimplementasi asli di Unix V7 dari akhir 70-an, dan sepertinya perilaku untuk alamat numerik tidak dimaksudkan atau setidaknya tidak dipikirkan sepenuhnya di sana.

Dengan Geoff membaca spec (dan interpretasi asli saya tentang mengapa hal itu terjadi), sebaliknya, di:

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

baris 1, 2, 4 dan 5 harus berupa output, karena kali ini, alamat akhir yang tidak pernah ditemui oleh 1,3pperintah jarak jauh, seperti diseq 5 | sed -n '3d;/1/,/3/p'

Namun, itu tidak terjadi pada implementasi asli, atau implementasi lain yang saya coba (busybox sedmengembalikan baris 1, 2 dan 4 yang lebih mirip bug).

Jika Anda melihat kode UNIX v7 , itu memeriksa kasus di mana nomor baris saat ini lebih besar dari alamat akhir (numerik), dan keluar dari jangkauan itu. Fakta bahwa ia tidak melakukannya untuk alamat awal lebih mirip pengawasan daripada desain yang disengaja.

Apa itu artinya adalah bahwa tidak ada implementasi yang benar-benar sesuai dengan interpretasi dari spesifikasi POSIX dalam hal ini saat ini.

Perilaku membingungkan lainnya dengan implementasi GNU adalah:

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

Karena baris 2 dilewati, maka 2,/3/baris 3 dimasukkan (baris pertama yang angkanya> = 2). Tetapi karena garis itulah yang membuat kami memasuki rentang, itu tidak memeriksa alamat akhir . Menjadi lebih buruk dengan busybox seddi:

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

Karena baris 2 hingga 7 dihapus, baris 8 adalah yang pertama yaitu> = 2 sehingga rentang 2,3 dimasukkan kemudian!

Stéphane Chazelas
sumber
1
Jadi sepertinya masalah ini masih belum terselesaikan - Saya setuju dengan alasan Anda mengapa hal itu terjadi, tetapi juga tidak jelas apakah itu yang diinginkan - meskipun itu juga terdengar seperti Geoff diyakinkan oleh teks yang dikutip bahwa implementasi UNIX ™ implementasi benar. Apakah itu bacaan Anda juga?
Michael Homer
1
@MichaelHomer, idenya adalah bahwa (menurut Geoff) POSIX jelas bahwa perilaku GNU tidak sesuai. Dan memang benar itu kurang konsisten (dibandingkan seq 10 | sed -n '1d;1,2p'dengan seq 10 | sed -n '1d;/^1$/,2p') bahkan jika berpotensi kurang mengejutkan bagi orang-orang tidak akan menyadari bagaimana rentang diproses. Tidak ada yang peduli melaporkannya sebagai bug ke orang-orang GNU. Saya tidak yakin saya akan memenuhi syarat sebagai bug, mungkin pilihan terbaik adalah memperbarui spesifikasi POSIX untuk memungkinkan kedua perilaku untuk memperjelas bahwa seseorang tidak dapat mengandalkan keduanya.
Stéphane Chazelas
2
Sebenarnya, karena definisi POSIX tidak membuat pernyataan bahwa alamat perlu "dilihat" untuk memulai atau mengakhiri rentang alamat, IMO implementasi GNU mengikuti kata-kata POSIX lebih ketat (mengejutkan untuk GNU!). Ini juga perilaku yang diinginkan untuk kebanyakan kasus dunia nyata yang saya tahu. Tetapi, seperti yang Anda tunjukkan, itu harus konsisten. Dan memeriksa setiap baris untuk pola rentang bahkan setelah dtidak hanya masalah kinerja, itu mengarah ke masalah implementasi lebih lanjut karena pola "tak terlihat" yang diperlukan untuk rentang tidak diperbolehkan memiliki efek pada pola kosong lebih lanjut ... berantakan!
Philippos
@ Pilipos, dalam 1d;1,2pskrip itu 1,2pperintah tidak dijalankan pada baris pertama, sehingga alamat pertama tidak cocok dengan ruang pola apa pun , yang merupakan salah satu cara untuk menafsirkan teks itu. Bagaimanapun, harus jelas bahwa evaluasi alamat harus dibuat pada saat perintah dijalankan. Seperti dised 's/./x/g; /xxx/,/xxx/d'
Stéphane Chazelas
2
@ Isaac, itulah inti dari masalah ini. Dalam bahasa POSIX 1dan /1/keduanya alamat, 1adalah alamat ketika nomor baris 1, /1/adalah alamat ketika ruang pola berisi 1, pertanyaannya adalah apakah kedua jenis alamat harus diperlakukan sama, atau jika rentang nomor baris harus dipertimbangkan " dalam absolut "terlepas dari apakah mereka cocok atau tidak. Lihat juga hasil edit terakhir saya untuk konteks yang lebih historis.
Stéphane Chazelas