Qual é o sentido de usar vários pontos de exclamação no sed?

12

A documentação do POSIX sed disse:

Uma função pode ser precedida por um ou mais '!' caracteres, caso em que a função será aplicada se os endereços não selecionarem o espaço do padrão. Zero ou mais caracteres <blank> serão aceitos antes do primeiro '!' personagem. Não é especificado se os caracteres <blank> podem seguir um '!' caractere e aplicativos conformes não devem seguir um '!' caractere com caracteres <blank>.

Assim, com qualquer POSIX sed, podemos:

sed -e '/pattern/!d' file

É o mesmo que escrever:

sed -e '/pattern/!!d' file

E !!!de nde exclamação marcas ainda estão ficar bem (testado com três sedversão de herança Toolchest ). Não vejo nenhum benefício entre vários em vez de uma exclamação.

Por que a especificação permitiu essa sintaxe e como é útil na aplicação no mundo real?


Parece que o GNU sed não é compatível neste caso, ele irá reclamar se usarmos várias exclamações:

$ sed -e '/pattern/!!d' file
sed: -e expression #1, char 11: multiple `!'s
cuonglm
fonte
2
FWIW: No OpenBSD !atua como uma alternância, /pattern/!!é o mesmo que /pattern/e /pattern/!!!é o mesmo que /pattern/!. No FreeBSD, múltiplos !são iguais a um único.
Lcd047
2
O ponto de muitas coisas na especificação é que os sedscripts podem ser gerados . Dado um POSIX sed, deve ser uma questão realmente simples escrever a escrita de um sedscript. Portanto, se você tiver algum gatilho para algum caso que marcará um endereço que !não seja digno de sua ação, você poderá acioná-lo várias vezes para o mesmo e ainda assim obter os mesmos resultados.
mikeserv
@cuonglm Não, apenas o FreeBSD é. Os GNU, OpenBSD e NetBSD seds não são.
Lcd047
@ lcd047: sim, é claro. Desculpe pelo meu inglês ruim. Quero dizer, não é compatível, é? É bom saber que. Mas o ponto principal na minha pergunta é como essa sintaxe pode ser útil no mundo real, com o POSIX sed?
cuonglm
1
FWIW: uma correção para isso foi confirmada no OpenBSD-current.
Lcd047

Respostas:

5

sedA API é primitiva - e isso é por design. Pelo menos, ele permaneceu primitivo por design - se ele foi concebido primitivamente desde o início, não posso dizer. Na maioria dos casos, a escrita de um sedscript que, quando executado, produzirá outro sedscript, é realmente uma questão simples. sedé frequentemente aplicado dessa maneira por pré-processadores de macro como m4e / ou make.

(O que se segue é um caso de uso altamente hipotético: é um problema projetado para se adequar a uma solução. Se parecer um esticamento para você, provavelmente é porque é, mas isso não necessariamente o torna menos válido.)


Considere o seguinte arquivo de entrada:

cat <<"" >./infile
camel
cat dog camel
dog cat
switch
upper
lower

Se desejássemos escrever um sedscript que anexasse a palavra -case à cauda de cada palavra apropriada no arquivo de entrada acima, apenas se ele pudesse ser encontrado em uma linha no contexto apropriado , e desejássemos fazê-lo da maneira mais eficiente possível ( como deve ser nosso objetivo, por exemplo, durante uma operação de compilação) , devemos preferir evitar aplicar o máximo de /regexp /s.

Uma coisa que podemos fazer é pré-editar o arquivo em nosso sistema agora e nunca ligar seddurante a compilação. Mas se alguma dessas palavras no arquivo deve ou não ser incluída com base nas configurações locais e / ou nas opções de tempo de compilação, isso provavelmente não seria uma alternativa desejável.

Outra coisa que podemos fazer é processar o arquivo agora contra regexps. Podemos produzir - e incluir em nossa compilação - um sedscript que pode aplicar edições de acordo com o número da linha - que normalmente é uma rota muito mais eficiente a longo prazo.

Por exemplo:

n=$(printf '\\\n\t')
grep -En 'camel|upper|lower' <infile |
sed "   1i${n%?}#!/usr/heirloom/bin/posix2001/sed -nf
        s/[^:]*/:&$n&!n;&!b&$n&/;s/://2;\$a${n%?}q"'
        s/ *cat/!/g;s/ *dog/!/g
        s| *\([cul][^ ]*\).*|s/.*/\1-case/p|'

... que grava a saída na forma de um sedscript e que se parece com ...

#!/usr/heirloom/bin/posix2001/sed -nf
:1
    1!n;1!b1
    1s/.*/camel-case/p
:2
    2!n;2!b2
    2!!s/.*/camel-case/p
:5
    5!n;5!b5
    5s/.*/upper-case/p
:6
    6!n;6!b6
    6s/.*/lower-case/p
q

Quando essa saída é salva em um arquivo de texto executável na minha máquina chamado ./bang.sede executado como ./bang.sed ./infile, a saída é:

camel-case
upper-case
lower-case

Agora você pode me perguntar ... Por que eu iria querer fazer isso? Por que eu não apenas ancorava grepos fósforos? Quem usa o estojo de camelo, afinal? E para cada pergunta que eu só podia responder, não faço ideia ... porque não faço. Antes de ler esta pergunta, eu nunca havia notado pessoalmente o multi-! requisito de análise nas especificações - acho que é uma captura bem legal.

O multi-! coisa que imediatamente faz sentido para mim, embora - a maior parte da sedespecificação é voltado para simplesmente analisados e simplesmente gerados sed scripts. Você provavelmente encontrará os \ndelimitadores de linha eletrônica necessários para [wr:bt{]fazer muito mais sentido nesse contexto, e se você mantiver essa ideia em mente, poderá entender melhor alguns outros aspectos da especificação - (como :não aceitar endereços e qse recusar a aceite mais de 1) .

No exemplo acima, eu escrevo uma certa forma de sedscript que só pode nunca ser lido uma vez. Se você observar bem, poderá perceber que, à medida que sedlê o arquivo de edição, ele progride de um bloco de comando para o próximo - ele nunca se afasta ou completa seu script de edição até que termine completamente com seu arquivo de edição.

Eu considero isso multi-! endereços podem ser mais úteis nesse contexto do que em alguns outros, mas, honestamente, não consigo pensar em um único caso em que eu possa tê-lo usado muito bem - e sedmuito. Também acho digno de nota que sedambos os GNU / BSD não conseguem lidar com isso conforme especificado - esse provavelmente não é um aspecto da especificação que é muito requisitado e, portanto, se uma implementação a ignora, duvido muito seriamente que seus bugs @ box sofrerão terrivelmente como resultado.

Dito isso, a falha em lidar com isso como especificado é um bug para qualquer implementação que pretenda conformidade, e por isso acho que enviar um email para as caixas de desenvolvimento relevantes é necessário aqui, e pretendo fazê-lo, se não o fizer.

mikeserv
fonte
1
Agora está corrigido no OpenBSD-atual.
Lcd047
1
Múltiplo !será removido na próxima especificação , o que está acontecendo aqui!
cuonglm
@ Cuonglm - tarde demais, eu acho. Talvez eu estivesse mais perto do que eu pensava.
mikeserv
@cuonglm - bem, ok, mas o que isso ... Aceito como marcado significa?
mikeserv
1
@ mikeserv: a resposta explicou minha maravilha e me deu outra visão com a API sed. Faz sentido para mim!
cuonglm