Nos comentários, foi descoberto que o arquivo de entrada está em big-endian UTF-16 , em vez de ASCII simples de 7 bits ou ASCII estendido de 8 bits. UTF-16 é um formato de 2 bytes por caractere e, se usado para codificar ASCII simples, o caractere "ASCII" tem 0x00
(um byte NUL, exibido como ^@
por cat -A
, less
, e outros programas) como o primeiro byte do par de 2 bytes (big-endian. invertido para little-endian).
A correção é converter o arquivo em ASCII simples. por exemplo. em vez de usar o utilitário padrão fromdos
ou similar para converter CR-LF (finais de linha dos / windows) para LF (finais de linha unix), você precisa fazer algo como o seguinte para converter o texto em um formato utilizável por o restante do script sed
:
sed -e '1 s/^\xff\xfe|^\xfe\xff//; s/\x00//g; s/\x0d$//'
Este script sed
:
- retira os marcadores de ordem de bytes
0xfffe
ou0xfeff
do início da primeira linha. - remove todos os caracteres NUL de todas as linhas de entrada, onde quer que ocorram.
- remove o caractere de retorno de carro (
0x0d
) do final de qualquer linha
Observação: isso é adequado apenas para texto codificado em UTF-16 que contenha apenas caracteres que, de outra forma, seriam ASCII. Ele manipulará completamente qualquer arquivo de texto UTF-16 que contenha outros tipos de caracteres (por exemplo, texto que não seja em inglês).
Por fim, perl
tem excelente suporte a texto em vários formatos comuns, incluindo ascii simples, UTF-8, UTF-16 e muito mais. Possui módulos de biblioteca para trabalhar e converter entre todos os formatos. É bastante fácil converter scripts sed
simples em perl
, portanto, uma versão perl do script pode ser tão simples quanto (não testada, mas pode até funcionar):
#!/usr/bin/perl
use strict;
use feature 'unicode_strings';
while(<>) {
s/^\xff\xfe|^\xfe\xff// if ($. == 1); # strip Byte Order marker from 1st line
s/\x0d$//; # strip CR from each end-of-line
s/ *"/"/g; # get rid of all spaces immediately before " characters
s/" */"/g; # get rid of all spaces immediately after " characters
# A very primitive split(). Should use a real CSV parser here, like the
# Text::CSV module which properly copes with embedded quotes and commas etc
# in string fields. This would also allow proper processing of each field to
# remove any extra whitespace characters rather than the quick-and-dirty hack of
# global regexp substitutions above.
my @fields = split /,/;
# perl arrays start from zero. This appends the "fake" field 42 onto field 41,
# and then deletes field 42.
$fields[40] .= $fields[41];
delete $fields[41];
print join(',',@fields), "\n";
}
Resposta antiga que ainda contém informações úteis (IMO):
awk
é uma ferramenta melhor para esse trabalho que sed
.
Por exemplo, com o GNU awk
(ou qualquer outro awk
que entenda o PCRE como \s
e \S
):
awk '{$0=gensub(/\s*(\S+)/,"\1",42)}1' original > fixed
Isso mescla a coluna 41 & 42 removendo quaisquer espaços imediatamente anteriores à coluna 42.
Para não-PCRE awk
, use [[:space:]]
em vez de \s
e [^[:space:]]
em vez de \S
:
awk '{$0=gensub(/[[:space:]]*(\[^[:space:]]+)/,"\1",42)}1' original > fixed
Além disso, dependendo da natureza exata do arquivo de entrada, perl
pode ser uma ferramenta ainda melhor para esse trabalho do que awk
. Por exemplo, ele possui módulos para analisar arquivos CSV e trabalhar com os campos individuais em um registro CSV.
BTW, IMO que sed
script é horrível, até porque você está usando vários -e
args em vez de um único script sed com ;
como separador de comando. Se você quiser usar sed
, use-a pelo menos de forma eficaz e eficiente. Seu script sed
é melhor escrito como:
sed -e 's/ \{1,\}"/"/g; s/" \{1,\}/"/g; s/","//41' original > fixed
ou até mesmo:
sed -e 's/ \{1,\}"/"/g
s/" \{1,\}/"/g
s/","//41' original > fixed
Você ainda precisa corrigir o bug, mas pelo menos você terá algo mais legível para depurar - o que torna muito mais fácil ver onde o problema pode estar.
Além disso, BTW, -i
ou --in-place
não é uma edição "no local" como você imagina. Ele funciona criando um arquivo temporário e depois colocando-o no lugar. Isso quebra tudo o que requer que o inode permaneça o mesmo, incluindo hard links.
É muito melhor gravar a saída alterada em um arquivo temporário (por exemplo, temp.txt) e, em seguida, em cat temp.txt > original.txt; rm temp.txt
- que sobrescreve o arquivo original com a versão alterada, mantendo o mesmo inode.