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 d
passasse 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 sed
duas linhas de entrada a
e b
. O programa faz duas coisas:
Exclui a primeira linha com
1d
. Od
comando seráExclua o espaço do padrão e inicie o próximo ciclo. e
- 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,2
nunca é atingido durante a linha 1 (porque d
já a
passou para o próximo ciclo / linha) e, portanto, a inclusão do intervalo nunca começa, enquanto foi excluída. Os Unix sed
s conformes do macOS e Solaris 10 produzem essa saída, assim como os não POSIX sed
no Solaris e BSD sed
em 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/d
para - 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 d
e 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?
ed
, ignorandosed
completamente?Respostas:
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:
E aqui está a parte relevante do restante da mensagem (por mim) que Geoff estava citando:
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'
comseq 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
sed
implementaçã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:
as linhas 1, 2, 4 e 5 devem ser exibidas, pois desta vez, é o endereço final que nunca é encontrado pelo
1,3p
comando à 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
sed
retorna 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 é:
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 combusybox sed
:Como as linhas 2 a 7 foram excluídas, a linha 8 é a primeira que é> = 2, então o intervalo 2,3 é inserido !
fonte
seq 10 | sed -n '1d;1,2p'
comseq 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.d
nã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!1d;1,2p
script o1,2p
comando 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'
1
e/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ém1
, 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.