Что 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 seds macOS и Solaris 10 выводят этот вывод, как и non-POSIX sedв Solaris и BSD sedв целом.

GNU sed, с другой стороны, печатает

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 sed 0,/a/d- это была попытка POSIX-совместимой передачи этого).

Было предложено вместо этого удалить до второго совпадения, /a/если совпадает первая строка (или весь файл, если второго совпадения нет), что кажется правдоподобным, но опять же, это делает только GNU sed. MacOS sed и Solaris sed производят

b
c
d
e

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

Очевидно, что на сегодняшний день этот код практически не переносится, учитывая несоответствие, но теоретически он должен быть везде эквивалентен с одним или другим значением. Я думаю, что это ошибка, но я не знаю, против какой реализации сообщать об этом. В настоящее время я считаю, что поведение GNU и Busybox sed несовместимо со спецификацией, но я могу ошибаться в этом.

Что здесь требуется POSIX?

Майкл Гомер
источник
В качестве временного решения, записать во временный файл и обработать его с POSIX ed, в обход sedвообще?
Д. Бен Кнобл

Ответы:

9

Это было поднято в списке рассылки Austin Group в марте 2012 года. Вот последнее сообщение об этом (Джефф Клэр из Austin Group (орган, который поддерживает 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.

Итак, (согласно Джеффу) 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, в этом 1d;1,2pсценарии 1,2pкоманда не выполняется в первой строке, поэтому первый адрес не соответствует ни одному пробелу , что является одним из способов интерпретации этого текста. В любом случае должно быть очевидно, что оценка адресов должна выполняться во время выполнения команды. Как вsed 's/./x/g; /xxx/,/xxx/d'
Стефан
2
@ Исаак, это суть проблемы. На языке POSIX 1и /1/являются оба адресами, 1это адрес , когда номер строки равен 1, /1/это адрес , когда шаблон пространство содержит 1, вопрос , является ли оба типа адреса должен быть обработан так же, или если диапазоны номеров линии должны рассматриваться " в абсолют "независимо от того, действительно ли они совпадают. Смотрите также мое последнее редактирование для более исторического контекста.
Стефан