Como recodificar em UTF-8 condicionalmente?

3

Estou unificando a codificação de um grande grupo de arquivos de texto, reunidos ao longo do tempo em computadores diferentes. Estou indo principalmente da ISO-8859-1 para a UTF-8. Isso converte muito bem um arquivo:

recode ISO-8859-1..UTF-8 file.txt

É claro que eu quero fazer o processamento em lote automatizado para todos os arquivos, e simplesmente executar o acima para cada arquivo tem o problema de que os arquivos codificados em UTF-8 terão sua codificação interrompida. (Por exemplo, o caractere 'a' originalmente na norma ISO-8859-1 aparece como esta, visto como UTF-8, se a recodificação acima é feito duas vezes: � -> ä -> ä)

Minha pergunta é: que tipo de script executaria a recodificação apenas se necessário , ou seja, apenas para arquivos que ainda não estavam na codificação de destino (UTF-8 no meu caso)?

Olhando para a página de manual de recodificação, não consegui descobrir como fazer algo assim. Então eu acho que isso se resume a como verificar facilmente a codificação de um arquivo, ou pelo menos se é UTF-8 ou não. Esta resposta implica que você pode reconhecer arquivos UTF-8 válidos com recodificação, mas como? Qualquer outra ferramenta também ficaria bem, desde que eu pudesse usar o resultado de forma condicional em um script bash ...

Jonik
fonte
Nota: Examinei perguntas como superuser.com/questions/27060/… e elas não fornecem uma resposta para essa pergunta em particular.
Jonik

Respostas:

7

Esta mensagem é bastante antiga, mas acho que posso contribuir para este problema:
Primeiro, crie um script chamado recodeifneeded :

#!/bin/bash
# Find the current encoding of the file
encoding=$(file -i "$2" | sed "s/.*charset=\(.*\)$/\1/")

if [ ! "$1" == "${encoding}" ]
then
# Encodings differ, we have to encode
echo "recoding from ${encoding} to $1 file : $2"
recode ${encoding}..$1 $2
fi

Você pode usá-lo desta maneira:

recodeifneeded utf-8 file.txt

Portanto, se você quiser executá-lo recursivamente e alterar todas as codificações de arquivos * .txt para (digamos) utf-8:

find . -name "*.txt" -exec recodeifneeded utf-8 {} \;

Eu espero que isso ajude.

Pierre FABIER
fonte
2
Única solução que funciona independentemente da codificação original.
Jr. Hames
3

Esse script, adaptado da idéia de harrymc , que recodifica um arquivo condicionalmente (com base na existência de certos caracteres escandinavos codificados em UTF-8), parece funcionar bem para mim de maneira tolerável.

$ cat recode-to-utf8.sh 

#!/bin/sh
# Recodes specified file to UTF-8, except if it seems to be UTF-8 already

result=`grep -c [åäöÅÄÖ] $1` 
if [ "$result" -eq "0" ]
then
    echo "Recoding $1 from ISO-8859-1 to UTF-8"
    recode ISO-8859-1..UTF-8 $1 # overwrites file
else
    echo "$1 was already UTF-8 (probably); skipping it"
fi

(O processamento de arquivos em lote é obviamente uma questão simples, por exemplo for f in *txt; do recode-to-utf8.sh $f; done.)

Nota : isso depende totalmente do arquivo de script ser UTF-8. E, como essa é obviamente uma solução muito limitada, adequada ao tipo de arquivo que tenho, fique à vontade para adicionar respostas melhores que resolvam o problema de uma maneira mais genérica.

Jonik
fonte
2

O UTF-8 possui regras estritas sobre quais seqüências de bytes são válidas. Isto significa que se os dados poderiam ser UTF-8, você raramente obter falsos positivos se você assumir que ele é .

Então você pode fazer algo assim (em Python):

def convert_to_utf8(data):
    try:
        data.decode('UTF-8')
        return data  # was already UTF-8
    except UnicodeError:
        return data.decode('ISO-8859-1').encode('UTF-8')

Em um script de shell, você pode usar iconvpara realizar a conversão, mas precisará de um meio de detectar UTF-8. Uma maneira é usar iconvcom UTF-8 como codificação de origem e de destino. Se o arquivo era UTF-8 válido, a saída será igual à entrada.

user46971
fonte
Obrigado, parece útil - Vou tentar isso na próxima vez quando lote que converte arquivos de texto
Jonik
1

O ISO-8859-1 e o UTF-8 são idênticos nos primeiros 128 caracteres. Portanto, seu problema é realmente como detectar arquivos que contenham caracteres engraçados, ou seja, codificados numericamente como acima de 128.

Se o número de caracteres engraçados não for excessivo, você pode usar o egrep para digitalizar e descobrir quais arquivos precisam ser recodificados.

harrymc
fonte
De fato, no meu caso, os "personagens engraçados" são principalmente apenas åäö (+ maiúsculas) usados ​​em finlandês. Não é tão simples assim, mas eu poderia adaptar essa idéia ... Estou usando o terminal UTF-8 e o grepping para, por exemplo, 'ä', encontra-o apenas em arquivos que já são UTF-8 (ou seja, nos próprios arquivos que eu quero) pular)! Portanto, devo fazer o oposto: recodificar arquivos em que o grep não encontra nenhum dos [äÄöÖåÅ] . Claro, para alguns desses arquivos (ascii puro) a recodificação não é necessária, mas também não importa. De qualquer forma, dessa maneira eu talvez faria com que todos os arquivos fossem UTF-8 sem quebrar os que já eram. Vou testar isso um pouco mais ...
Jonik
1

Estou um pouco atrasado, mas tenho lutado tantas vezes com a mesma pergunta várias vezes ... Agora que encontrei uma ótima maneira de fazer isso, não posso deixar de compartilhá-lo :)

Apesar de ser um usuário do emacs, recomendo que você use o vim hoje.

com este comando simples, ele irá recodificar seu arquivo, independentemente da codificação desejada:

vim +'set nobomb | set fenc=utf8 | x' <filename>

nunca encontrei algo que me desse melhores resultados do que isso.

Espero que ajude alguns outros.

user2380653
fonte
0

Você pode detectar e adivinhar o conjunto de caracteres de um arquivo usando

file -bi your_file_with_strange_encoding.txt

Este forro do bash one usa o comando acima como a entrada para recodificar e fazer loop em vários arquivos:

for f in *.txt; do recode -v "`file -bi ${f} | grep -o 'charset=.*' | cut -f2- -d=`..utf-8" ${f}; done

Não se preocupe em converter o utf-8 existente, a recodificação é inteligente o suficiente para não fazer nada nesse caso e imprimiria uma mensagem:

Request: *mere copy*
Phlogi
fonte
0

Existem várias maneiras de detectar um conjunto de caracteres e nenhum é 100% confiável. Isso ajuda muito se os possíveis idiomas e conjuntos de caracteres forem limitados e você tiver texto suficiente para contar bytes específicos.

Outra abordagem é tentar recode(usando a recodificação ) e verificar o valor de saída quanto a erros.

Para diferenciar apenas UTF-8 e ISO-8859-X para idiomas usando caracteres latinos, um truque é tentar recodificar primeiro em UTF-16. Ele funcionará para UTF-8 ou sairá com um erro para ISO-8859-X.

Às vezes eu uso isso em um script:

# UTF-16 or non-text binary ?
if grep -P -q '[\0-\x08\x0B\x0C\x0E-\x1F]' "$file" ; then
    if cat "$file" | recode -s utf16/..utf8 &>/dev/null ; then
        echo "utf-16"
    else
        echo "BINARY?"
    fi
    exit
fi

# plain ASCII ?
if ! grep -P -q '[\x7F-\xFF]' "$file" ; then
    echo "ASCII"
    exit
fi

# UTF-8 or Latin1/CP1252 ?
# order of tests is important!
for charset in utf8 latin1 cp1252 ; do
    if cat "$file" | recode -s $charset/..utf16 &>/dev/null ; then
        found=$charset
        if [ "$found" == "latin1" ]; then
            # checking if latin1 is really cp1252
            if grep -P -q '[\x80-\x9F]' "$file" ; then
                found=cp1252
            fi
        fi
        break
    fi
done

if [ -n "$found" ]; then
    echo "$found"
else
    echo "UNKNOWN"
fi
mivk
fonte