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".
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?
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
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
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.
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
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
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%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.
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).
$ 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
Tags text-processing awk sed scripting