O que o POSIX sed requer para `1d; 1,2d` onde um intervalo de endereços começa a partir de uma linha já excluída?

11

Nos comentários a esta pergunta , surgiu um caso em que várias implementações sed discordavam de um programa bastante simples, e nós (ou pelo menos eu) não conseguimos determinar o que a especificação realmente exige para isso.

O problema é o comportamento de um intervalo que começa em uma linha excluída:

1d;1,2d

A linha 2 deve ser excluída mesmo que o início do intervalo tenha sido removido antes de atingir esse comando? Minha expectativa inicial era "não", de acordo com o BSD sed, enquanto o GNU sed diz "yes" e a verificação do texto da especificação não resolve completamente o problema.

Correspondendo às minhas expectativas estão (pelo menos) macOS sed, Solaris e BSD sed. Discordam são (pelo menos) GNU e Busybox sed, e numerosas pessoas aqui. Os dois primeiros são certificados pelo SUS, enquanto os outros provavelmente são mais difundidos. Qual comportamento está correto?


O texto de especificação para intervalos de dois endereços diz:

O utilitário sed deve então aplicar em seqüência todos os comandos cujos endereços selecionam esse espaço padrão, até que um comando inicie o próximo ciclo ou saia.

e

Um comando de edição com dois endereços deve selecionar o intervalo inclusivo do primeiro espaço de padrão que corresponde ao primeiro endereço até o próximo espaço de padrão que corresponde ao segundo. [...] Começando na primeira linha após o intervalo selecionado, sed deve procurar novamente o primeiro endereço. Depois disso, o processo deve ser repetido.

Indiscutivelmente, a linha 2 está dentro "do intervalo inclusivo do primeiro espaço de padrão que corresponde ao primeiro endereço até o próximo espaço de padrão que corresponde ao segundo", independentemente de o ponto inicial ter sido excluído. Por outro lado, eu esperava que o primeiro dpassasse para o próximo ciclo e não desse ao intervalo uma chance de começar. As implementações certificadas com UNIX ™ fazem o que eu esperava, mas potencialmente não o que a especificação exige.

Seguem alguns experimentos ilustrativos, mas a questão principal é: o que deve ser sed feito quando um intervalo começa em uma linha excluída?


Experiências e exemplos

Uma demonstração simplificada do problema é esta, que imprime cópias extras de linhas em vez de excluí-las:

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

Isso fornece sedduas linhas de entrada ae b. O programa faz duas coisas:

  1. Exclui a primeira linha com 1d. O dcomando será

    Exclua o espaço do padrão e inicie o próximo ciclo. e

  2. Selecione o intervalo de linhas de 1 a 2 e as imprima explicitamente, além da impressão automática que toda linha recebe. Uma linha incluída no intervalo deve aparecer duas vezes.

Minha expectativa era que isso fosse impresso

b

somente, com o intervalo não sendo aplicado porque 1,2nunca é atingido durante a linha 1 (porque dapassou para o próximo ciclo / linha) e, portanto, a inclusão do intervalo nunca começa, enquanto foi excluída. Os Unix seds conformes do macOS e Solaris 10 produzem essa saída, assim como os não POSIX sedno Solaris e BSD sedem geral.

O GNU sed, por outro lado, imprime

b
b

indicando que ele tem interpretado o intervalo. Isso ocorre no modo POSIX e não. O sed do Busybox tem o mesmo comportamento (mas nem sempre o mesmo comportamento, por isso não parece ser resultado de código compartilhado).

Experimentação adicional com

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'

descobre que parece tratar um intervalo que começa em uma linha excluída como se fosse iniciado na linha seguinte . Isso é visível porque /c/não corresponde ao final do intervalo. Usar /b/para iniciar o intervalo não se comporta da mesma forma que 2.


O exemplo inicial de trabalho que eu estava usando era

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

como uma maneira de excluir todas as linhas até a primeira /a/correspondência, mesmo que isso esteja na primeira linha (o que o GNU sed usaria 0,/a/dpara - essa foi uma tentativa de versão compatível com POSIX).

Foi sugerido que isso exclua até a segunda correspondência de /a/se a primeira linha corresponder (ou o arquivo inteiro, se não houver uma segunda correspondência), o que parece plausível - mas, novamente, apenas o GNU sed faz isso. Tanto o macOS sed como o sed do Solaris produzem

b
c
d
e

por isso, como eu esperava (o GNU sed produz a saída vazia da remoção do intervalo não terminado; o Busybox sed imprime apenas de e, o que está claramente errado, não importa o quê). Geralmente, eu diria que a aprovação nos testes de conformidade da certificação significa que seu comportamento está correto, mas muitas pessoas sugeriram o contrário, não tenho certeza, o texto da especificação não é completamente convincente e a suíte de testes não pode ser perfeitamente abrangente.

Claramente, não é praticamente portátil escrever esse código hoje, dada a inconsistência, mas teoricamente deve ser equivalente em todo lugar a um significado ou outro. Eu acho que isso é um bug, mas não sei contra quais implementações denunciar. Minha opinião atualmente é que o comportamento do GNU e do Busybox sed é inconsistente com a especificação, mas eu posso estar enganado sobre isso.

O que o POSIX exige aqui?

Michael Homer
fonte
Como solução temporária, grave em um arquivo temporário e processe-o com o POSIX ed, ignorando sedcompletamente?
D. Ben Knoble 9/08/19

Respostas:

9

Isso foi levantado na lista de discussão do grupo Austin em março de 2012. Aqui está a mensagem final (por Geoff Clare, do Austin Group (o órgão que mantém o POSIX), que também foi quem levantou a questão em primeiro lugar). Aqui copiado da interface gmane NNTP:

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

E aqui está a parte relevante do restante da mensagem (por mim) que Geoff estava citando:

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.

Portanto, (de acordo com Geoff), o POSIX está claro que o comportamento do GNU não é compatível.

E é verdade que é menos consistente (compare seq 10 | sed -n '1d;1,2p'com seq 10 | sed -n '1d;/^1$/,2p') mesmo que seja potencialmente menos surpreendente para as pessoas que não percebem como os intervalos são processados ​​(até Geoff inicialmente achou o comportamento em conformidade "estranho" ).

Ninguém se incomodou em denunciá-lo como um bug para o pessoal do GNU. Não tenho certeza se o qualificaria como um bug. Provavelmente, a melhor opção seria a atualização da especificação POSIX para permitir que ambos os comportamentos deixassem claro que não se pode confiar em nenhum deles.

Edit . Agora, observei a sedimplementação original no Unix V7, no final dos anos 70, e parece que esse comportamento para endereços numéricos não foi intencional ou, pelo menos, não foi completamente pensado lá.

Com a leitura de Geoff das especificações (e minha interpretação original de por que isso acontece), inversamente, em:

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

as linhas 1, 2, 4 e 5 devem ser exibidas, pois desta vez, é o endereço final que nunca é encontrado pelo 1,3pcomando à distância, como emseq 5 | sed -n '3d;/1/,/3/p'

No entanto, isso não acontece na implementação original, nem em nenhuma outra implementação que eu tentei (o busybox sedretorna as linhas 1, 2 e 4, que mais parecem um bug).

Se você observar o código UNIX v7 , ele verifica o caso em que o número da linha atual é maior que o endereço final (numérico) e sai do intervalo. O fato de não fazer isso no endereço inicial parece mais uma supervisão do que um design intencional.

O que isso significa é que não há implementação que seja realmente compatível com essa interpretação da especificação POSIX nesse sentido no momento.

Outro comportamento confuso com a implementação do GNU é:

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

Como a linha 2 foi pulada, a 2,/3/entrada é inserida na linha 3 (a primeira linha cujo número é> = 2). Mas como é a linha que nos levou a inserir o intervalo, não é verificado o endereço final . Fica pior com busybox sed:

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

Como as linhas 2 a 7 foram excluídas, a linha 8 é a primeira que é> = 2, então o intervalo 2,3 é inserido !

Stéphane Chazelas
fonte
1
Parece que o problema ainda não foi resolvido - concordo com o seu raciocínio sobre o porquê de estar acontecendo, mas também não está claro se era isso que se queria - embora pareça também que Geoff ficou convencido pelo texto citado de que o UNIX ™ implementa estavam corretos. Essa é a sua leitura também?
Michael Homer
1
@MichaelHomer, a ideia é que (de acordo com Geoff) o POSIX fique claro que o comportamento do GNU não é compatível. E é verdade que é menos consistente (compare seq 10 | sed -n '1d;1,2p'com seq 10 | sed -n '1d;/^1$/,2p'), mesmo que potencialmente menos surpreendente para as pessoas não percebam como os intervalos são processados. Ninguém se incomodou em denunciá-lo como um bug para o pessoal do GNU. Não tenho certeza se o qualificaria como um bug, provavelmente a melhor opção seria atualizar a especificação POSIX para permitir que ambos os comportamentos deixassem claro que não se pode confiar também.
Stéphane Chazelas 9/08/19
2
Na verdade, como a definição POSIX não declara que os endereços precisam ser "vistos" para iniciar ou terminar um intervalo de endereços, IMO, a implementação GNU segue a formulação POSIX com mais rigor (surpreendente para o GNU!). Esse também é um comportamento desejado para a maioria dos casos do mundo real que conheço. Mas, como você ressalta, precisaria ser consistente. E verificar cada linha quanto a padrões de intervalo, mesmo depois de dnão ser apenas um problema de desempenho, leva a outros problemas de implementação, pois os padrões "invisíveis" necessários para os intervalos não podem afetar outros padrões vazios ... uma bagunça!
Philippos
@ Philippos, nesse 1d;1,2pscript o 1,2pcomando não é executado na primeira linha, portanto o primeiro endereço não é correspondido por nenhum espaço de padrão , que é uma maneira de interpretar esse texto. De qualquer forma, deve ser óbvio que a avaliação dos endereços deve ser feita no momento em que o comando é executado. Like insed 's/./x/g; /xxx/,/xxx/d'
Stéphane Chazelas 9/08/19
2
@ Isaac, esse é o cerne da questão. No idioma POSIX 1e /1/são os dois endereços, 1é o endereço quando o número da linha é 1, /1/é o endereço quando o espaço do padrão contém 1, a questão é se os dois tipos de endereço devem ser tratados da mesma forma ou se os intervalos de número de linha devem ser considerados " no absoluto "independentemente de corresponderem. Veja também minha edição mais recente para um contexto mais histórico.
Stéphane Chazelas 9/08/19