Como procurar pela palavra armazenada no espaço de espera com sed?

3

Esta é uma questão específica de sed ; Estou ciente de que isso poderia ser feito com outras ferramentas, mas estou trabalhando para expandir meu conhecimento sobre sed .

Como posso usar sed para citar globalmente (na verdade, backtick) uma palavra que não esteja especificada no script? A palavra é mantida no espaço de espera.

O que eu quero é algo como:

s/word/'&'/g

Mas o truque é que word será contido não no script sed, mas no espaço de armazenamento. Então parece algo mais parecido com:

H
g
s/^\(.*\)\n\(.*\)\(.*\)$/''/

que irá citar uma ocorrência da palavra mantida no espaço de espera. Quero citar todos deles, mas não posso simplesmente adicionar um sinalizador g , devido ao modo como ele usa referências anteriores em vez de expressões regulares estáticas.

H
g
s/^\(.*\)\n\(.*\)\(.*\)\(.*\)$/''''/

Isso lida com duas ocorrências da palavra, mas falha em uma e ignora mais de uma.

Eu achei que poderia usar algo limpo e simples como:

s//'&'/g

Mas isso reutiliza o último regex , não o que corresponde. (O que faz sentido.)

Existe alguma maneira em sed de fazer o que estou tentando fazer? (Na verdade, eu estaria interessado em ver como isso seria fácil em perl , mas eu ainda gostaria de ver como fazer isso em sed .)

UPDATE

Não que seja necessário para essa pergunta, mas achei que daria um pouco mais de contexto sobre o que exatamente estava fazendo quando surgiu essa pergunta:

Eu tinha um grande arquivo de texto de documentação, algumas partes precisavam ser condensadas e resumidas em uma tabela asciidoc . Foi bem fácil por causa das Description: e Prototype: linhas, etc., então eu realmente escrevi um script rápido de sed para fazer toda a análise para mim. Funcionou lindamente - mas a única coisa que faltava era que eu queria voltar a colocar as palavras na linha Description que correspondia aos argumentos listados na linha Prototype . As linhas do protótipo se pareciam com isso:

Prototype: some_words_here(and, arg, list,here)

Havia mais de 200 entradas diferentes na tabela que eu estava produzindo (e a documentação de origem incluía muito mais texto do que isso) e cada arglist precisava apenas ser usado para citar as palavras correspondentes em um linha. Para tornar as coisas mais complicadas, alguns dos args não estavam na linha Description, alguns estavam em mais de uma vez e alguns arglists estavam vazios ().

No entanto, dado que às vezes um argumento corresponderia a uma parte de uma palavra, que eu não queria receber backticked, e algumas vezes um nome arg era uma palavra comum (como from ) que eu só queria ser backticked quando era usado no contexto de explicar o uso da função, uma solução automatizada não era realmente um bom ajuste e eu usei vim para fazer o trabalho semi-manualmente, com a ajuda de algumas macros complicadas. :)

    
por Wildcard 27.10.2015 / 18:25

2 respostas

4

Isso foi difícil. Supondo que você tenha um file assim:

$ cat file
word
line with a word and words and wording wordy words.

Onde:

  • Linha 1: é o padrão de pesquisa que deve ser mantido no espaço de espera e cotado para 'word' .
  • Linha 2: é a linha para pesquisar e substituir globalmente.

O comando sed :

sed -n '1h; 2{x;G;:l;s/^\([^\n]\+\)\n\(.*[^']\)\([^']\)/\n''/;tl;p}' file

Explicação :

  • 1h; salve a primeira linha no espaço de espera (isso é esperar que queremos procurar).
      O espaço de retenção
    • contém: word
  • 2{...} aplica-se à segunda linha.
  • x; troca o espaço padrão e o espaço de espera.
  • G; acrescenta o espaço de espera ao espaço do padrão. No espaço de padrões que temos agora:
word # I will call this line the "pattern line" from now on
line with a word and words and wording wordy words.
  • :l; definiu um rótulo chamado l como ponto para mais tarde.
  • s/// faz a pesquisa / substituição real no espaço de padrão mencionado acima:
    • ^\([^\n]\+\)\n procura na "linha de padrão" para todos os caracteres (desde o início da linha ^ ) que não são uma nova linha [^\n] (uma ou mais vezes \+ ), até uma nova linha \n . Agora isso é armazenado na referência de referência . Contém a "linha padrão".
    • (.*[^']) procura por qualquer caractere .* seguido por um caractere, que não é um [^'] do backtick. Isso é armazenado em . contém agora: line with a word and words and wording wordy , até a última ocorrência de word , porque ...
    • é o próximo termo de pesquisa (a referência de referência , word ), portanto, o que a "linha padrão" contém.
    • ([^']) isto é seguido por outro caracter que não é um backtick; salvo para fazer referência a . Se não fizermos isso (e a parte em acima), terminaríamos em um loop infinito citando o mesmo word , de novo e de novo - > ''''word'''' , porque s/// sempre seria bem-sucedido e tl; voltará para :l (consulte tl; mais abaixo).
    • \n \ 3 todos os itens acima são substituídos pelas referências anteriores. O segundo é o que devemos citar (note que a primeira referência é a "linha padrão").
  • tl; se o s/// foi bem-sucedido (substituímos algo) pula para o rótulo chamado l e começa novamente até que não haja mais nada para pesquisar e substituir. Este é o caso, quando todas as ocorrências de palavras são substituídas / citadas.
  • p; quando tudo estiver pronto, imprima a linha alterada (espaço padrão).

A saída:

$ sed -n '1h; 2{x;G;:l;s/^\([^\n]\+\)\n\(.*[^']\)\([^']\)/\n''/;tl;p}' file
word
line with a 'word' and 'word's and 'word'ing 'word'y 'word's.
    
por 27.10.2015 / 20:13
3

As tabelas de consulta podem ser difíceis - e caras - porque você precisa pesquisar as duas extremidades do espaço do padrão simultaneamente. Pode, pelo menos, ser implementado mais ou menos diretamente, no entanto. Você tem que considerar que não importa o que você faça, você só pode lidar de forma confiável com um único jogo de cada vez, e assim você pode desistir de qualquer esperança de um resultado g lobal aqui. Ele só vai confundir as coisas de qualquer maneira - você não está trabalhando com uma expressão compilada, você está literalmente trabalhando com efeitos colaterais e ambos para inicializar.

printf  %s\n some words to match \
        'and some words and some more words to match them against' |
sed  -ne'$!{H;d;}' -e'G;s/\(\n\).*/&/;tm' -e:m \
     -e 's/\(.\)\(.*\)\(.*\n\n.*\n\(\n\)\)/''/;tm'

Esse é o loop principal. Na verdade, ainda não funciona porque não o limpo ainda, mas resolve o problema fundamental. Como você tem que fazer um loop repetidamente no mesmo espaço de padrão, como você pode ter certeza de que sua correspondência não combina duas vezes, certo? Se você fizer um bookend com algum delimitador, você continuará igualando novamente, e você apenas acumulará os bookends ad infinitum.

A solução que eu uso aqui é para manejar o jogo. Eu insiro uma nova linha após o primeiro personagem da partida, que eu ainda preciso limpar, é claro, e que eu vou lidar com um momento. Isso ainda não funciona, no entanto, se as tabelas de pesquisa puderem conter membros que sejam subconjuntos de outros membros ou se você estiver trabalhando com conjuntos de caracteres únicos. Existem maneiras de fazer isso - e melhores maneiras de fazer isso - e vou oferecer algumas alternativas se você pedir por elas.

Aqui está um pouco mais:

printf  %s\n some words to match \
        'and some words and some more words to match them against' |
sed  -ne'$!{H;d;}' -e'G;s/\(\n\).*/&/;tm' -e:m \
     -e 's/\(.\)\(.*\)\(.*\n\n.*\n\(\n\)\)/''/;tm' \
     -e  l
and 's\nome' 'w\nords' and 's\nome' more 'w\nords' 't\no' 'm\natch' \
them against\n\n\nsome\nwords\nto\nmatch\n$

E a limpeza é fácil, claro:

printf  %s\n some words to match \
        'and some words and some more words to match them against' |
sed  -ne'$!{H;d;}' -e'G;s/\(\n\).*/&/;tm' -e:m \
     -e 's/\(.\)\(.*\)\(.*\n\n.*\n\(\n\)\)/''/;tm' \
     -e 's/\('.\)\n//g;P'
and 'some' 'words' and 'some' more 'words' 'to' 'match' them against

Isso, pelo menos, você pode fazer g lobally.

Meu método preferido de fazer esse tipo de coisa é realmente criar um script para ele.

printf  %s\n some words to match \
        'and some words and some more words to match them against' |
{   sed -e"$(
        sed -ne'$w /dev/fd/3' -e$\q     \
             -e 's/[]\^$/.*[]/\&/g'    \
             -e 's|..*|s/&/'\&'/g|p'
    )"  <&3
}   3<<""    3<>/dev/fd/3
and 'some' 'words' and 'some' more 'words' 'to' 'match' them against

O sed dentro da substituição de comando grava uma declaração sed s/// ubstitution depois de tomar cuidado para escapar de quaisquer metacaracteres que qualquer linha de entrada, mas a última, possa conter. A última linha que w escreve literalmente para o descritor de arquivo here-doc compartilhado para o outer sed para ler como entrada. O sed interno imprime um script que funciona como:

sed -e's/some/'&'/g'  \
    -e's/words/'&'/g' \
    -e's/to/'&'/g'    \
    -e's/match/'&'/g'

... e entrega a última linha para o outro sed para lidar com isso depois.

    
por 28.10.2015 / 10:28