Como fazer uma substituição sed (s///g) com base em uma lista? Preciso trocar várias palavras, por outras palavras correspondentes

Como fazer uma substituição sed (s///g) com base em uma lista? Preciso trocar várias palavras, por outras palavras correspondentes

Não acho que essa pergunta tenha sido feita antes, então não sei se sedé capaz disso.

Suponha que eu tenha um monte de números em uma frase que preciso expandir em palavras, um exemplo prático é trocar as citações numeradas de um ensaio típico para o formato MLA:

essay.txt:

Sentence 1 [1]. sentence two [1][2]. Sentence three[1][3].

Key.txt(este é um arquivo delimitado por tabulações):

1   source-one
2   source-two
3   source-three
...etc

Esperado Result.txt:

Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three]

Aqui está minha tentativa de pseudocódigo, mas não entendo o suficiente sedou trnão faço direito:

 cat essay.txt | sed s/$(awk {print $1} key.txt)/$(awk {print $2} key.txt)/g

PS: Se houver um truque no notepad ++ para localizar e substituir em massa usando vários termos, isso seria ótimo. Do jeito que está, parece que localizar e substituir funciona apenas para um termo de cada vez, mas preciso encontrar uma maneira de fazer isso em massa para muitos termos ao mesmo tempo.

Responder1

Você deve usar perlem vez disso:

$ perl -ne '
  ++$nr;
  if ($nr == $.) {
    @w = split;
    $k{$w[0]} = $w[1];
  }
  else {
    for $i (keys %k) {
      s/(\[)$i(\])/$1.$k{$i}.$2/ge
    }
    print;
  }
  close ARGV if eof;
' key.txt essay.txt
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three]

Responder2

awkpode fazer efetivamente o mesmo que perlaquium pouco mais simples, embora implementações diferentes do GNU possam desperdiçar um pouco de tempo de CPU dividindo desnecessariamente o arquivo de texto (grande?):

awk 'NR==FNR{a["\\["$1"\\]"]="["$2"]";next} {for(k in a) gsub(k,a[k]);print}' key.txt essay.txt

Desde que você pediuexplicação:

  • awkopera pegando um 'script' que consiste em pares padrão-ação, depois lê um ou mais arquivos (ou entrada padrão), um 'registro' por vez, onde por padrão cada registro é uma linha, e para cada registro o divide em campos por padrão no espaço em branco (que inclui tabulação) e aplica o script por sua vez (a menos que seja orientado de outra forma) testando cada padrão (que geralmente olha para o registro atual e/ou seus campos) e se ele corresponde à execução da ação (que geralmente faz algo para ou com o referido registro e/ou campos). Aqui eu especifico dois arquivos key.txt essay.txtpara que ele leia esses dois arquivos nessa ordem, linha por linha. O roteiropodeser colocado em um arquivo em vez de na linha de comando, mas aqui optei por não fazê-lo.

  • o primeiro padrão é NR==FNR. NRé uma variável interna que é o número do registro que está sendo processado; FNRé da mesma forma o número do registro no arquivo de entrada atual. Para o primeiro arquivo ( key.txt) estes são iguais; para o segundo arquivo (e quaisquer outros) eles são desiguais

  • a primeira ação é {a["\\["$1"\\]"]="["$2"]";next}. awkpossui matrizes 'associativas' ou 'hashed'; arrayname[subexpr]onde subexpré uma expressão com valor de string que lê ou define um elemento da matriz. $numberpor exemplo $1 $2, etc faz referência aos campos e $0faz referência a todo o registro. Conforme acima, esta ação é executada apenas para linhas in, key.txtpor exemplo, na última linha desse arquivo $1is 3e $2is source-three, e isso armazena uma entrada de array com um subscrito \[3\]e um conteúdo de [source-three]; veja abaixo por que escolhi esses valores. Os "\\["e "\\]"são literais de string usando escapes cujos valores reais são \[e \]while "[" "]"são apenas [ ], e operandos de string sem operador entre eles são concatenados. Finalmente esta ação é executada, nexto que significa pular o resto do script para este registro, apenas voltar ao topo do loop e iniciar no próximo registro.

  • o segundo padrão está vazio, portanto corresponde a todas as linhas do segundo arquivo e executa a ação {for(k in a) gsub(k,a[k]);print}. A for(k in a)construção cria um loop, bem como os shells do tipo Bourne fazem em for i in this that other; do something with $i; done, exceto que aqui os valores de ksão ossubscritosda matriz a. Para cada valor, ele executa gsub(substituto global) que encontra todas as correspondências de uma determinada expressão regular e as substitui por uma determinada string; Eu escolhi os subscritos e o conteúdo da matriz (acima) para que, por exemplo, \[3\]seja uma expressão regular que corresponda à string de texto [3]e [source-three]seja a string de texto que você deseja substituir para cada correspondência. gsubopera no registro atual $0por padrão. Depois de fazer essa substituição para todos os valores, aele executa printo que, por padrão, é exibido $0como está agora, com todas as substituições desejadas feitas.

Nota: GNU awk (gawk), que é comum especialmente no Linux, mas não universal, tem uma otimização onde na verdade não faz a divisão de campos se nada nos padrões ou ações executadas precisar dos valores dos campos. Em outras implementações, uma pequena quantidade de tempo de CPU pode ser desperdiçada, o que o método do cuonglm perlevita, mas a menos que seus arquivos sejam enormes, isso provavelmente nem será perceptível.

Responder3

bash$ sed -f  <( sed -rn 's#([0-9]+)\s+(.*)#s/\\[\1]/[\2]/g#p' key.txt ) essay.txt

Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three].

Responder4

Você pode usar a substituição sed no local dentro de um loop para conseguir isso:

$ cp essay.txt Result.txt
$ while read n k; do sed -i "s/\[$n\]/\[$k\]/g" Result.txt; done < key.txt
$ cat Result.txt 
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three].

informação relacionada