Substituição de string usando um dicionário

7

O que é uma boa maneira de fazer substituições de string em um arquivo usando um dicionário com muito de pares substituintes-substituintes? E por muito , eu realmente quero dizer cerca de 20 - não muito, mas muitos o suficiente para que eu queira organizá-los perfeitamente.

Eu meio que quero coletar todos os pares substituintes-substituintes em um arquivo dictionary.txt de uma maneira fácil de gerenciar, já que preciso substituir muitas coisas, por exemplo:

"yes"      : "no"
"stop"     : "go, go, go!"
"wee-ooo"  : "ooooh nooo!"
"gooodbye" : "hello"

"high"     : "low"
"why?"     : "i don't know"

Agora quero aplicar essas substituições em algum arquivo novel.txt .

Em seguida, quero executar magiccommand --magicflags dictionary.txt novel.txt para que todas as instâncias de yes em novel.txt sejam substituídas por no (até mesmo Bayesian seja substituído por Banoian ) e todas as instâncias de goodbye em novel.txt seria substituído por hello e assim por diante.

Até agora, as strings que preciso substituir (e substituir por) não têm nenhuma aspas (nem simples nem duplas) nelas. (Seria bom, no entanto, ver uma solução funcionando bem com strings contendo citações, é claro.)

Eu sei que sed e awk / gawk podem fazer essas coisas principalmente, mas eles também podem trabalhar com esses arquivos de dicionário? Parece que gawk seria o candidato certo para magiccommand , quais são os magicflags corretos? Como preciso formatar meu dictionary.txt ?

    
por k.stm 12.03.2016 / 17:15

3 respostas

3

Aqui está uma maneira com sed :

sed '
s|"\(.*\)"[[:blank:]]*:[[:blank:]]*"\(.*\)"|\
|
h
s|.*\n||
s|[\&/]|\&|g
x
s|\n.*||
s|[[\.*^$/]|\&|g
G
s|\(.*\)\n\(.*\)|s///g|
' dictionary.txt | sed -f - novel.txt

Como funciona:
O primeiro sed transforma dictionary.txt em um arquivo de script (comandos de edição, um por linha). Isso é canalizado para o segundo sed (observe o -f - que significa comandos de leitura de stdin ) que executa esses comandos, editando novel.txt .
Isso requer a tradução do seu formato

"STRING"   :   "REPLACEMENT"

em um comando sed e escapando de qualquer caractere especial no processo para LHS e RHS :

s/ESCAPED_STRING/ESCAPED_REPLACEMENT/g

Então a primeira substituição

s|"\(.*\)"[[:blank:]]*:[[:blank:]]*"\(.*\)"|\
|

transforma "STRING" : "REPLACEMENT" em STRING\nREPLACEMENT ( \n é um caractere de nova linha). O resultado é então copiado sobre o espaço h old.
s|.*\n|| exclui a primeira parte mantendo apenas REPLACEMENT , em seguida, s|[\&/]|\&|g escapa dos caracteres reservados (esse é o RHS ).
Em seguida, e x altera o buffer de retenção com o espaço de padrão e s|\n.*|| exclui a segunda parte, mantendo apenas STRING e s|[[\.*^$/]|\&|g fazendo o escape (esse é o LHS ).
O conteúdo do buffer de retenção é então anexado ao espaço de padrão via G , então agora o conteúdo do espaço de padrão é ESCAPED_STRING\nESCAPED_REPLACEMENT .
A substituição final

s|\(.*\)\n\(.*\)|s///g|

transforma em s/ESCAPED_STRING/ESCAPED_REPLACEMENT/g

    
por 12.03.2016 / 20:00
1

Aqui está uma versão perl. Ele cria um hash contendo expressões regulares pré-compiladas e, em seguida, percorre cada linha de entrada aplicando todas as expressões regulares a cada linha. perl ' -i é usado para "edição no local" do arquivo de entrada. Você pode adicionar ou alterar facilmente qualquer uma das expressões regulares ou sequências de substituição.

A pré-compilação das regexes usando qr// melhora bastante a velocidade do script, o que será muito perceptível se houver muitas expressões regulares e / ou muitas linhas de entrada para processar.

#! /usr/bin/perl -i

use strict;

# the dictionary is embedded in the code itself.
# see 2nd version below for how to read dict in
# from a file.
my %regex = (
    qr/yes/      => 'no',
    qr/stop/     => 'go, go, go!',
    qr/wee-ooo/  => 'ooooh nooo!',
    qr/gooodbye/ => 'hello',
    qr/high/     => 'low',
    qr/why\?/    => 'i don\'t know',
);

while (<>) {
      foreach my $key (keys %regex) {
            s/$key/$regex{$key}/g;
      }
}

Aqui está outra versão que lê no dicionário a partir do primeiro nome de arquivo na linha de comando, enquanto ainda processa os segundos nomes de arquivo (e subsequentes opcionais):

#! /usr/bin/perl -i

use strict;

# the dictionary is read from a file.
#
# file format is "searchpattern replacestring", with any
# number of whitespace characters (space or tab) separating
# the two fields.  You can add comments or comment out dictionary
# entries with a '#' character.
#
# NOTE: if you want to use any regex-special characters as a
# literal in either $searchpattern or $replacestring, you WILL
# need to escape them with '\'.  e.g. for a literal '?', use '\?'.
#
# this is very basic and could be improved.  a lot.

my %regex = ();

my $dictfile = shift ;
open(DICT,'<',$dictfile) || die "couldn't open $dictfile: $!\n";
while(<DICT>) {
    s/#.*// unless (m/\#/); # remove comments, unless escaped.
                             # easily fooled if there is an escaped 
                             # '#' and a comment on the same line.

    s/^\s*|\s*$//g ;         # remove leading & trailing spaces
    next if (/^$/) ;         # skip empty lines

    my($search, $replace) = split;
    $regex{qr/$search/} = $replace;
};
close(DICT);


# now read in the input file(s) and modify them.
while (<>) {
      foreach my $key (keys %regex) {
            s/$key/$regex{$key}/g;
      }
}
    
por 13.03.2016 / 03:04
1

Comecei a escrever isso como um comentário, mas ficou muito complicado, daí uma segunda resposta perl. Dado o seu arquivo de origem, você pode usar um truque perl puro para construir um regex:

#!/usr/bin/env perl

use strict;
use warnings; 
use Data::Dumper;

#build key-value pairs
my %replace = map { /"(.+)"\s*:\s*"(.+)"/ } <DATA>;
print Dumper \%replace; 

#take the keys of your hash, then build into capturing regex
my $search = join ( "|", map {quotemeta} keys %replace ); 
$search = qr/($search)/;

print "Using match regex of: $search\n";

#read stdin or files on command line, line by line
while ( <> ) { 
    #match regex repeatedly, replace with contents of hash. 
    s/$search/$replace{$1}/g;
    print;
}

__DATA__
"yes"      : "no"
"stop"     : "go, go, go!"
"wee-ooo"  : "ooooh nooo!"
"gooodbye" : "hello"

"high"     : "low"
"why?"     : "i don't know"

Geramos um hash usando uma correspondência de padrão de várias linhas e map para criar pares de valores-chave.

Criamos uma regex de pesquisa e usamos os valores capturados nela para substituir.

Usar <> é o filehandle mágico do perl - STDIN ou arquivos especificados na linha de comando. Quanto sed faz isso. (Você pode usar um arquivo e lê-lo 'normalmente' para o padrão, o uso de DATA é puramente ilustrativo).

    
por 13.03.2016 / 23:27