Önceden silinmiş bir satırdan bir adres aralığının başladığı POSIX sed `1d; 1,2d` için ne gerektirir?


11

Bu sorunun yorumlarında, çeşitli sed uygulamalarının oldukça basit bir programa katılmadığı bir durum ortaya çıktı ve biz (veya en azından ben) spesifikasyonun aslında bunun için ne gerektirdiğini belirleyemedik.

Sorun, silinen bir satırdan başlayan bir aralığın davranışıdır:

1d;1,2d

Bu komuta ulaşmadan önce aralığın başlangıcı kaldırılmış olsa bile 2. satır silinmeli mi? İlk beklentim BSD sed ile "hayır" iken, GNU sed "evet" diyor ve şartname metnini kontrol etmek sorunu tamamen çözmüyor.

Beklentilerimi karşılayan (en azından) macOS ve Solaris sedve BSD sed. Katılmama (en azından) GNU ve Busybox sedve burada çok sayıda insan var. İlk ikisi SUS sertifikalı, diğerleri ise daha yaygın. Hangi davranış doğrudur?


Şartname metni iki adres aralıkları için şöyle diyor:

Sed Komut sonraki döngüsü veya sonlandırılıyor başlayıncaya kadar yarar, sonra sırası Adresleri örüntü alanı seçmek tüm komutlar geçerli olacaktır.

ve

İki adresli bir düzenleme komutu, ilk adresle eşleşen ilk desen alanından ikinciyle eşleşen bir sonraki desen alanına dahil olan aralığı seçmelidir. [...] Seçilen aralığı takip eden ilk satırdan başlayarak, sed ilk adresi arar. Daha sonra işlem tekrarlanacaktır.

Muhtemelen, satır 2 olduğu içinde bakılmaksızın başlangıç noktası silinmiştir bakılmaksızın, "ikinci maçları sonraki model uzayda ilk adresiyle eşleşen ilk desen uzaydan dahil aralık". Öte yandan, ilkinin bir dsonraki döngüye geçmesini ve aralığa başlama şansı vermemesini bekledim . UNIX ™ sertifikalı uygulamalar beklediğim şeyi yapıyor ancak potansiyel olarak şartnamenin zorunlu kıldığı şeyleri yapmıyor.

Bazı açıklayıcı deneyler takip eder, ancak kilit soru şudur: bir aralık silinen bir satırda başladığında ne yapmalı sed ?


Deneyler ve örnekler

Sorunun basitleştirilmiş bir gösterimi, satırları silmek yerine fazladan kopyalar basan şudur:

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

Bu, sediki satır giriş sağlar ave b. Program iki şey yapar:

  1. İle ilk satırı siler 1d. dkomut olacak

    Desen alanını silin ve bir sonraki döngüyü başlatın. ve

  2. Her satırın otomatik olarak yazdırılmasına ek olarak, 1 ila 2 arasındaki satır aralığını seçin ve bunları açıkça yazdırın. Böylece aralığa dahil edilen bir çizgi iki kez görünmelidir.

Benim beklentim bunun yazdırılması gerektiğiydi

b

yalnızca, uygulama aralığı kullanılmadığı için 1,2, satır 1 sırasında hiçbir zaman ulaşılmadığı için ( dzaten bir sonraki döngüye / satıra atlandığı için) ve böylece aralık dahil etme işlemi asla asilinmezken başlamaz . sedMacOS ve Solaris 10'un uyumlu Unix'leri , genel olarak sedSolaris ve BSD'de POSIX olmayanlar gibi bu çıktıyı üretir sed.

Diğer yandan GNU sed

b
b

bu işaret etti aralığı yorumlanır. Bu hem POSIX modunda gerçekleşir, hem de olmaz. Busybox'ın sed aynı davranışa sahiptir (ancak her zaman aynı davranış değildir, bu nedenle paylaşılan kodun bir sonucu değildir).

İle daha fazla deney

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'

silinmiş bir satırdan başlayarak bir aralığı bir sonraki satırdan başlıyormuş gibi görür . Bu /c/aralık görünür çünkü aralığın sonunu getirmiyor. Kullanılması /b/aralığını başlatmak için yok değil aynı davranırlar 2.


Kullandığım ilk çalışma örneği

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

ilk /a/hatta kadar olan tüm satırları silmenin bir yolu olarak , bu ilk satırda olsa bile (GNU sed'in ne kullanacağı 0,/a/d- bu POSIX uyumlu bir yorumlama girişimiydi).

Yerine kadar silmelisiniz ileri sürülmüştür ikinci maç /a/mantıklı görünüyor, ilk satır maçları (hayır ikinci eşleşme veya tüm dosya ise) eğer - ama yine sadece GNU bunu yapmaz sed. Hem macOS sed hem de Solaris'in sed üretimi

b
c
d
e

bunun için, beklediğim gibi (GNU sed boş çıktıyı sonlandırılmamış aralığı kaldırmaktan üretir; Busybox sed sadece yazdırır dve ene olursa olsun açıkça yanlıştır). Genellikle sertifikasyon uygunluk testlerini geçtiklerini, davranışlarının doğru olduğu anlamına gelir, ancak yeterli sayıda insan başka türlü emin olmadığımı önerdi, şartname metni tamamen ikna edici değil ve test takımı olamaz mükemmel kapsamlı.

Açıkçası, bu tutarsızlık nedeniyle bugün bu kodu yazmak pratik olarak taşınabilir değildir, ancak teorik olarak her yerde bir anlam veya diğeri ile eşdeğer olmalıdır. Bunun bir hata olduğunu düşünüyorum, ancak hangi uygulama (lar) ı bildireceğini bilmiyorum. Şu anda benim görüşüme göre GNU ve Busybox sed'in davranışı şartname ile tutarsız, ama bu konuda yanılmış olabilirim.

POSIX burada ne gerektirir?


Geçici bir çözüm olarak, geçici bir dosyaya yazıp POSIX ile birlikte işlem yaparak ed, sedtamamen atlayarak ?
D. Ben Knoble

Yanıtlar:


9

Bu, Mart 2012'de Austin grubu e-posta listesinde gündeme getirildi. İşte bununla ilgili son mesaj (Austin Group'tan (POSIX'i muhafaza eden gövde) ve aynı zamanda konuyu en başta dile getiren kişi). Gmane NNTP arayüzünden kopyalandı:

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

Ve işte mesajın geri kalanının (benim tarafımdan) Geoff'un alıntıladığı ilgili kısmı:

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.

Dolayısıyla, (Geoff'a göre) POSIX, GNU davranışının uyumlu olmadığı açıktır .

Ve (karşılaştırmak daha az tutarlı doğrudur seq 10 | sed -n '1d;1,2p'ile seq 10 | sed -n '1d;/^1$/,2p'bile aralıkları nasıl işlendiğini fark etmez insanlara potansiyel olarak daha az şaşırtıcı olmadığını (hatta Geoff başlangıçta uygun davranışı bulundu) "garip" ).

Kimse bunu bir hata olarak GNU insanlarına bildirmekten rahatsız olmadı. Bir böcek olarak nitelendireceğime emin değilim. Muhtemelen en iyi seçenek, POSIX belirtiminin her iki davranışın da ikisinin de güvenemeyeceğini açıkça belirtmesine izin verecek şekilde güncellenmesi olacaktır.

Düzenle . Şimdi sed70'lerin sonlarından itibaren Unix V7'deki orijinal uygulamaya bir göz attım ve sayısal adresler için bu davranışın amaçlanmadığı veya en azından tamamen orada düşünülmediği gibi görünüyor.

Geoff'in spesifikasyonu okuması (ve neden olduğuna dair orijinal yorumum), tersine, şu şekilde:

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

satır 1, 2, 4 ve 5 çıktılanmalıdır, çünkü bu sefer, 1,3paralık komutu tarafından hiç karşılaşılmayan bitiş adresidir , örneğinseq 5 | sed -n '3d;/1/,/3/p'

Ancak, bu orijinal uygulamada ya da denediğim diğer uygulamalarda gerçekleşmez (busybox sed, daha çok bir hataya benzeyen 1, 2 ve 4 satırlarını döndürür).

UNIX v7 koduna bakarsanız , geçerli satır numarasının (sayısal) bitiş adresinden büyük olduğu durumu kontrol eder ve o zaman aralık dışına çıkar. Aslında bu başlangıç adresi için bunu yapmaz kasıtlı bir tasarım daha sonra daha bir gözetim benziyor.

Bunun anlamı şu anda POSIX spesifikasyonunun bu yorumuna gerçekten uyan hiçbir uygulamanın olmadığıdır.

GNU uygulamasıyla ilgili bir başka kafa karıştırıcı davranış:

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

Satır 2 atlandığından beri satır 2,/3/3'e (numarası> = 2 olan ilk satır) girilir. Ancak aralığa girmemizi sağlayan satır olduğundan , bitiş adresi için kontrol edilmemiştir . İle daha da kötüleşir busybox sed:

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

Satır 2 ila 7 silindiğinden, ilk satır 8 => 2'dir, böylece 2,3 aralığı girilir !


1
Bu yüzden sorun hala çözülmemiş gibi görünüyor - neden olduğuna dair mantığınıza katılıyorum, ama aynı zamanda istenen şeyin bu olup olmadığı da belli değil - yine de Geoff, UNIX ™ uygulamalarının alıntılanan metin tarafından ikna edilmiş gibi görünüyor doğruydu. Bu da senin okuman mı?
Michael Homer

1
@MichaelHomer, fikir (Geoff'a göre) POSIX'in GNU davranışının uyumlu olmadığı açıktır . Ve o (karşılaştırmak az tutarlı doğrudur seq 10 | sed -n '1d;1,2p'ile seq 10 | sed -n '1d;/^1$/,2p'potansiyel olarak daha az insanlara şaşırtıcı aralıkları nasıl işlendiğini fark etmez olur bile). Kimse bunu bir hata olarak GNU insanlarına bildirmekten rahatsız olmadı. Ben bir hata olarak nitelendirmek emin değilim, muhtemelen en iyi seçenek POSIX spec her iki davranış ya da bir güvenemez açık olmasını sağlamak için güncellemek olacaktır.
Stéphane Chazelas

2
Aslında, POSIX tanımı bir adres aralığını başlatmak veya sonlandırmak için adreslerin "görülmesi" gerektiği anlamına gelmediğinden, IMO GNU uygulaması POSIX ifadesini daha kesin bir şekilde takip eder (GNU için şaşırtıcı!). Bu aynı zamanda bildiğim çoğu gerçek dünya vakası için istenen davranıştır. Ancak, belirttiğiniz gibi, tutarlı olması gerekir. Ve her bir çizgiyi menzil kalıpları için sonradan kontrol etmek dsadece bir performans sorunu değildir, aralıklar için gereken "görünmeyen" kalıpların daha fazla boş kalıp üzerinde etkisi olmasına izin verilmediği için daha fazla uygulama sorununa yol açar ... karışıklık!
Philippos

@Philippos, ki 1d;1,2psenaryo 1,2pböylece komut, ilk satırda çalıştırılmaz ilk adres ile eşleşmeyen herhangi desen alanı bu metni yorumlamak için bir yol olduğunu,. Her durumda, adreslerin değerlendirilmesinin komut çalıştırıldığı sırada yapılması gerektiği açıktır. Gibi içindesed 's/./x/g; /xxx/,/xxx/d'
Stéphane Chazelas

2
@Isaac, sorunun özü bu. POSIX dilinde 1ve /1/her iki 1adres de, satır numarası 1 /1/olduğunda adres, kalıp alanı içerdiği adrestir 1, soru her iki adres türünün de aynı şekilde ele alınıp alınmayacağı veya satır numarası aralıklarının dikkate alınması gerekip gerekmediğidir " mutlak bir şekilde ". Daha tarihsel bağlam için son düzenlememe de bakın.
Stéphane Chazelas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.