Um script que exclui espaços extras entre letras no texto

12

Eu tenho um documento de texto com uma carga de texto que tem um espaço extra adicionado após cada letra!

Exemplo:

T h e  b o o k  a l s o  h a s  a n  a n a l y t i c a l  p u r p o s e  w h i c h  i s  m o r e  i m p o r t a n t…

Visualmente:

T␣h␣e␣␣b␣o␣o␣k␣␣a␣l␣s␣o␣␣h␣a␣s␣␣a␣n␣␣a␣n␣a␣l␣y␣t␣i␣c␣a␣l␣␣p␣u␣r␣p␣o␣s␣e␣␣w␣h␣i␣c␣h␣␣i␣s␣␣m␣o␣r␣e␣␣i␣m␣p␣o␣r␣t␣a␣n␣t…

Note que existe um espaço extra após cada letra, então há dois espaços entre palavras consecutivas.

Existe uma maneira de obter awk ou sed para excluir os espaços extras? (Infelizmente este documento de texto é enorme e levaria muito tempo para passar manualmente.) Eu aprecio que este seja provavelmente um problema muito mais complexo para resolver com apenas um simples script bash, pois também precisa haver algum tipo de reconhecimento de texto.

Como posso abordar esse problema?

    
por lloowen 10.09.2016 / 14:09

10 respostas

16

A regex a seguir removerá o primeiro espaço em qualquer sequência de espaços. Isso deve fazer o trabalho.

s/ ( *)//g

Então, algo como:

perl -i -pe 's/ ( *)//g' infile.txt

... substituirá infile.txt por uma versão "fixa".

    
por 11.09.2016 / 07:33
17

Use wordsegment , um pacote NLP de segmentação de palavras puro:

$ pip install wordsegment
$ python2.7 -m wordsegment <<<"T h e b o o k a l s o h a s a n a n a l y t i c a l p u r p o s e w h i c h i s m o r e i m p o r t a n t"
the book also has an analytical purpose which is more important
    
por 10.09.2016 / 19:21
13

Com base no fato de que a entrada inclui espaços duplos entre as palavras, há uma solução muito mais simples. Você simplesmente altera os espaços duplos para um caractere não utilizado, remove os espaços e altera o caractere não utilizado de volta para um espaço:

echo "T h e  b o o k  a l s o  h a s  a n  a n a l y t i c a l  p u r p o s e  w h i c h  i s  m o r e  i m p o r t a n t  " | sed 's/  /\-/g;s/ //g;s/\-/ /g'

... saídas:

The book also has an analytical purpose which is more important

    
por 10.09.2016 / 22:10
10

Perl para o resgate!

Você precisa de um dicionário, ou seja, um arquivo com uma palavra por linha. No meu sistema, existe como /var/lib/dict/words , eu também vi arquivos similares como /usr/share/dict/british etc.

Primeiro, você se lembra de todas as palavras do dicionário. Em seguida, você lê a linha de entrada por linha e tenta adicionar caracteres a uma palavra. Se for possível, lembre-se da palavra e tente analisar o resto da linha. Se você chegar ao final da linha, você sai na linha.

#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

my $words = '/var/lib/dict/words';
my %word;

sub analyze {
    my ($chars, $words, $pos) = @_;
    if ($pos == @$chars) {
        $_[3] = 1;  # Found.
        say "@$words";
        return
    }
    for my $to ($pos .. $#$chars) {
        my $try = join q(), @$chars[ $pos .. $to ];
        if (exists $word{$try}) {
            analyze($chars, [ @$words, $try ], $to + 1, $_[3]);
        }
    }
}


open my $WORDS, '<', $words or die $!;
undef @word{ map { chomp; lc $_ } <$WORDS> };

while (<>) {
    my @chars = map lc, /\S/g;
    analyze(\@chars, [], 0, my $found = 0);
    warn "Unknown: $_" unless $found;
}

Para sua entrada, ele gera 4092 leituras possíveis no meu sistema.

    
por 10.09.2016 / 14:53
6

Observação: essa resposta (como algumas outras aqui) é baseada em uma versão anterior da pergunta em que as palavras não foram delimitadas. A versão mais recente pode ser respondida trivialmente .

Em uma entrada como:

T h e b o o k a l s o h a s a n a n a l y t i c a l p u r p o s e w h i c h i s m o r e i m p o r t a n t

Você pode tentar:

 $ tr -d ' ' < file | grep -oiFf /usr/share/dict/words | paste -sd ' '
 The book also has ana na l y tic al purpose which ism ore important

Ele processa da esquerda para a direita e encontra uma palavra mais longa depois da próxima.

Obviamente, aqui, não é a melhor seleção de palavras, pois a sentença não faz sentido, mas para encontrar a correta, você precisa de ferramentas capazes de entender a gramática ou o significado do texto ou menos algumas informações estatísticas sobre as palavras que provavelmente serão encontradas juntas para chegar ao conjunto mais provável de palavras. Parece que a solução é uma biblioteca especializada encontrada por Lynn

    
por 10.09.2016 / 18:48
2

Semelhante à versão de Dewi Morgan, mas com sed:

$ echo "f o o  t h e  b a r" | sed -r "s/[ ]{1}([^ ]{1})//g"
foo the bar
    
por 12.09.2016 / 00:59
1

Embora pudesse (e devesse) ser feito com um one-liner Perl, um pequeno analisador de C seria muito rápido também, e também é muito pequeno (e esperançosamente muito correto):

#include <stdio.h>
#include <stdlib.h>

int main()
{
  char c1 = '
gcc-4.9 -O3 -g3  -W -Wall -Wextra -std=c11 lilcparser.c -o lilcparser
', c2 = '
echo "T h e  b o o k  a l s o  h a s  a n  a n a l y t i c a l  p u r p o s e  w h i c h  i s  m o r e  i m p o r t a n t  " | ./lilcparser
', tmp_c; c1 = fgetc(stdin); for (;;) { if (c1 == EOF) { break; } c2 = fgetc(stdin); if (c2 == EOF) { if (c1 != ' ') { fputc(c1, stdout); } break; } if (c1 == c2 && c1 == ' ') { tmp_c = fgetc(stdin); if (tmp_c != EOF) { if (tmp_c != '\n') { ungetc(tmp_c, stdin); fputc(' ', stdout); } else { ungetc(tmp_c, stdin); } } else { break; } } else if (c1 != ' ') { fputc(c1, stdout); } c1 = c2; } exit(EXIT_SUCCESS); }

Compilado com

#include <stdio.h>
#include <stdlib.h>

int main()
{
  char c1 = '
gcc-4.9 -O3 -g3  -W -Wall -Wextra -std=c11 lilcparser.c -o lilcparser
', c2 = '
echo "T h e  b o o k  a l s o  h a s  a n  a n a l y t i c a l  p u r p o s e  w h i c h  i s  m o r e  i m p o r t a n t  " | ./lilcparser
', tmp_c; c1 = fgetc(stdin); for (;;) { if (c1 == EOF) { break; } c2 = fgetc(stdin); if (c2 == EOF) { if (c1 != ' ') { fputc(c1, stdout); } break; } if (c1 == c2 && c1 == ' ') { tmp_c = fgetc(stdin); if (tmp_c != EOF) { if (tmp_c != '\n') { ungetc(tmp_c, stdin); fputc(' ', stdout); } else { ungetc(tmp_c, stdin); } } else { break; } } else if (c1 != ' ') { fputc(c1, stdout); } c1 = c2; } exit(EXIT_SUCCESS); }

(programm é um pouco menor que 9kb)

Use em um tubo como, por exemplo:

%pre%     
por 12.09.2016 / 00:30
1

Eu tentei isso e parece funcionar:

echo "<text here>" | sed -r 's/(\w)(\s)//g'

O comando sed captura dois grupos e retorna apenas o primeiro.

    
por 12.09.2016 / 11:26
0

Em c ++, eu faria isso:

#include <fstream>
using namespace std;

int main()
{   
    fstream is("test.txt", std::ios::in);

    char buff;
    vector<char>str;

    while (!is.eof()){is.get(buff);str.push_back(buff);} //read file to string

    for (int a=0;a<str.size();++a)if (str[a] == ' ' && str[a + 1] != ' ')str.erase(str.begin()+a);
    is.close();

    ofstream os("test.txt", std::ios::out | std::ios::trunc); //clear file for rewrite

    os.write(str.data(), str.size() * sizeof(char)); //write chars
    os.close();

    return 0;
    }

Alterará o conteúdo do arquivo de texto de teste para a mesma sequência, mas com espaços entre as letras removidas. (Requer um espaço entre cada letra para ser preciso).

    
por 12.09.2016 / 04:57
0
$ echo 'F o u r  s c o r e  a n d' | \
txr -t '(mapcar* (opip (split-str @1 "  ")
                       (mapcar (op regsub #/ / ""))
                       (cat-str @1 " "))
                 (get-lines))'
Four score and


$ txr -e '(awk (:begin (set fs "  "))
               ((mf (regsub #/ / ""))))'  # mf: modify fields
F o u r  s c o r e  a n d
Four score and


$ awk -F'  ' '{for(i=1;i<=NF;i++)gsub(/ /,"",$i);print}'
F o u r  s c o r e  a n d
Four score and
    
por 14.09.2016 / 17:07