Що потрібно POSIX sed для `1d; 1,2d`, де діапазон адрес починається з уже видаленого рядка?


11

У коментарях до цього питання з’явився випадок, коли різні впровадження sed не погоджувались у досить простій програмі, і ми (або принаймні я) не змогли визначити, що саме для цього вимагає специфікація.

Проблема полягає в поведінці діапазону, що починається з видаленого рядка:

1d;1,2d

Чи слід видалити рядок 2, навіть якщо початок діапазону було видалено до досягнення цієї команди? Моє первісне очікування було "ні" відповідно до BSD sed, тоді як GNU sed каже "так", і перевірка тексту специфікації не повністю вирішує питання.

Мої очікування відповідають (принаймні), macOS і Solaris sed, і BSD sed. Не погоджуються (принаймні) 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 sedз macOS і Solaris 10 дають цей вихід, як і не-POSIX sedв Solaris і BSD sedв цілому.

GNU sed, з іншого боку, друкує

b
b

вказуючи , що він має інтерпретований діапазон. Це відбувається як в режимі POSIX, так і не. Сед 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 sed використовував би 0,/a/d- це було спробою сумісного POSIX-видання).

Запропоновано, що замість цього слід видалити до другого збігу, /a/якщо перший рядок збігається (або весь файл, якщо немає другого збігу), що здається правдоподібним - але знову ж таки, це робить лише GNU sed. Випускаються як macOS sed, так і sedlar Solaris

b
c
d
e

для цього, як я і очікував (GNU sed видає порожній вихід, видаляючи неперерваний діапазон; Busybox sed друкує просто, dі eце явно неправильно незалежно від того, що). Як правило, я припускаю, що те, що вони пройшли тести на відповідність сертифікації, означають, що їх поведінка правильна, але достатньо людей підказало інакше, що я не впевнений, текст специфікації не є абсолютно переконливим, і тестовий набір не може бути ідеально всебічний.

Зрозуміло, що писати цей код сьогодні, зважаючи на невідповідність, практично не переносно, але теоретично він повинен бути скрізь еквівалентний тому чи іншому значенню. Я думаю, що це помилка, але я не знаю, проти якої реалізації (-ів) повідомити про це. Наразі я вважаю, що поведінка GNU та Busybox sed не відповідає специфікації, але я можу помилитися з цим.

Що тут вимагає POSIX?


Як тимчасове вирішення записуйте у тимчасовий файл і обробляйте його POSIX ed, sedповністю обходячи його ?
D. Ben Knoble

Відповіді:


9

Це було піднято в списку розсилки групи Остін у березні 2012 року. Ось остаточне повідомлення про це (Джефф Клер з групи Остін (орган, який підтримує POSIX), який також є тим, хто порушив це питання в першу чергу). Тут скопійовано з інтерфейсу gmane NNTP:

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

І ось відповідна частина решти повідомлення (мною), яке цитував Джефф:

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команда ранжирування, як уseq 5 | sed -n '3d;/1/,/3/p'

Тим не менш, це не відбувається в оригінальній реалізації, ні будь-яка інша реалізація, яку я спробував (busybox sedповертає рядки 1, 2 і 4, що більше нагадує помилку).

Якщо ви подивитеся на код UNIX v7 , він перевіряє на випадок, коли номер поточного рядка більше, ніж (числовий) кінцевий адресу, і виходить із діапазону тоді. Той факт, що він не робить це для стартової адреси, більше схожий на недогляд, ніж на навмисний дизайн.

Це означає, що на даний момент немає жодної реалізації, яка фактично відповідала б інтерпретації специфікації POSIX.

Ще одна заплутана поведінка щодо впровадження 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
Тож звучить так, що проблема досі не вирішена - я погоджуюся з вашими міркуваннями, чому це відбувається, але також незрозуміло, що саме цього хотіли - хоча це також звучить так, як Джефф переконаний цитованим текстом, що реалізація UNIX ™ були правильними. Це читання також?
Майкл Гомер

1
@MichaelHomer, ідея полягає в тому, що (за словами Джеффа) POSIX зрозуміло, що поведінка GNU не відповідає. І це правда , це менш послідовно (порівняйте seq 10 | sed -n '1d;1,2p'з seq 10 | sed -n '1d;/^1$/,2p') , навіть якщо потенційно менш дивно для людей б не розуміють , як діапазони обробляються. Ніхто не заважав повідомити це про помилку людям GNU. Я не впевнений, що я визнав би це помилкою, мабуть, найкращим варіантом було б оновити специфікацію POSIX, щоб обидва типи поведінки дали зрозуміти, що не можна покладатися ні на одне.
Стефан Шазелас

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, питання полягає в тому, чи слід обидва типи адреси обробляти однаковими, чи слід вважати діапазони номерів рядків " в абсолюті "незалежно від того, чи відповідали вони. Дивіться також мою останню редакцію для більш історичного контексту.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.