В комментариях к этому вопросу возник случай, когда различные реализации 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
. Программа делает две вещи:
Удаляет первую строку с
1d
.d
Команда будетУдалите пространство шаблона и начните следующий цикл. и
- Выберите диапазон строк от 1 до 2 и распечатайте их явно, в дополнение к автоматической печати, которую получает каждая строка. Таким образом, линия, включенная в диапазон, должна появиться дважды.
Я ожидал, что это должно напечатать
b
только с диапазоном, который не применяется, потому что 1,2
никогда не достигается в строке 1 (потому что уже d
перешел к следующему циклу / строке), и поэтому включение диапазона никогда не начинается, пока a
оно было удалено. Соответствующие Unix sed
s 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?
ed
, в обходsed
вообще?Ответы:
Это было поднято в списке рассылки Austin Group в марте 2012 года. Вот последнее сообщение об этом (Джефф Клэр из Austin Group (орган, который поддерживает POSIX), который также является тем, кто поднял проблему в первую очередь). Вот скопированный из интерфейса gmane NNTP:
И вот соответствующая часть остальной части сообщения (мной), которое цитировал Джефф:
Итак, (согласно Джеффу) POSIX ясно, что поведение GNU несовместимо.
И это правда, что он менее последовательный (по сравнению
seq 10 | sed -n '1d;1,2p'
сseq 10 | sed -n '1d;/^1$/,2p'
), даже если он потенциально менее удивителен для людей, которые не понимают, как обрабатываются диапазоны (даже Джефф первоначально счел соответствующее поведение «странным» ).Никто не удосужился сообщить об этом как об ошибке пользователям GNU. Я не уверен, что квалифицировал бы это как ошибку. Вероятно, лучшим вариантом было бы обновить спецификацию POSIX, чтобы оба поведения давали понять, что нельзя полагаться ни на одно из них.
Редактировать . Теперь я взглянул на оригинальную
sed
реализацию в Unix V7 с конца 70-х годов, и похоже, что поведение числовых адресов не было задумано или, по крайней мере, не было полностью продумано.С прочтением Джеффом спецификации (и моей первоначальной интерпретацией того, почему это происходит), наоборот, в:
строки 1, 2, 4 и 5 должны быть выведены, потому что на этот раз это конечный адрес, который никогда не встречается командой
1,3p
ранжирования, как вseq 5 | sed -n '3d;/1/,/3/p'
Тем не менее, этого не происходит ни в оригинальной реализации, ни в любой другой, которую я пробовал (busybox
sed
возвращает строки 1, 2 и 4, что больше похоже на ошибку).Если вы посмотрите на код UNIX v7 , он проверит случай, когда текущий номер строки больше (числового) конечного адреса, и затем выйдет за пределы диапазона. Тот факт, что он не делает это для начального адреса, больше похож на упущение, чем на намеренный дизайн.
Это означает, что на данный момент не существует реализации, действительно соответствующей этой интерпретации спецификации POSIX.
Другое запутанное поведение с реализацией GNU:
Поскольку строка 2 была пропущена,
2,/3/
она вводится в строке 3 (первая строка с номером> = 2). Но так как это линия, которая заставила нас войти в диапазон, она не проверяется на конечный адрес. Это ухудшается сbusybox sed
в:Так как строки 2-7 были удалены, строка 8 является первой>> 2, поэтому вводится диапазон 2,3 !
источник
seq 10 | sed -n '1d;1,2p'
сseq 10 | sed -n '1d;/^1$/,2p'
), даже если потенциально менее удивительный для людей не понять, как обрабатываются диапазоны. Никто не удосужился сообщить об этом как об ошибке пользователям GNU. Я не уверен, что квалифицировал бы это как ошибку, вероятно, лучшим вариантом было бы обновить спецификацию POSIX, чтобы оба поведения давали понять, что нельзя полагаться ни на одно из них.d
является не только проблемой производительности, но и дополнительными проблемами реализации, так как «невидимые» шаблоны, необходимые для диапазонов, не могут влиять на дальнейшие пустые шаблоны ... беспорядок!1d;1,2p
сценарии1,2p
команда не выполняется в первой строке, поэтому первый адрес не соответствует ни одному пробелу , что является одним из способов интерпретации этого текста. В любом случае должно быть очевидно, что оценка адресов должна выполняться во время выполнения команды. Как вsed 's/./x/g; /xxx/,/xxx/d'
1
и/1/
являются оба адресами,1
это адрес , когда номер строки равен 1,/1/
это адрес , когда шаблон пространство содержит1
, вопрос , является ли оба типа адреса должен быть обработан так же, или если диапазоны номеров линии должны рассматриваться " в абсолют "независимо от того, действительно ли они совпадают. Смотрите также мое последнее редактирование для более исторического контекста.