web-dev-qa-db-fra.com

comment détecter unicode/binaire utf8 invalide dans un fichier texte

Je dois détecter un fichier texte corrompu contenant des caractères utf-8, Unicode ou binaires invalides (non-ASCII). 

�>t�ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½w�ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿ï¿½ï¿½ï¿½ï¿½ï¿½o��������ï¿ï¿½_��������������������o����������������������￿����ß����������ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½~�ï¿ï¿½ï¿½ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½}���������}w��׿��������������������������������������ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½~������������������������������������_������������������������������������������������������������������������������^����ï¿ï¿½s�����������������������������?�������������ï¿ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½w�������������ï¿ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿ï¿½}����������ï¿ï¿½ï¿½ï¿½ï¿½y����������������ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½o�������������������������}��

ce que j'ai essayé:

iconv -f utf-8 -t utf-8 -c file.csv 

ceci convertit un fichier d'encodage utf-8 en encodage utf-8 et -c permet d'ignorer les caractères utf-8 non valides. Cependant, à la fin, ces caractères illégaux sont toujours imprimés. Existe-t-il d'autres solutions dans bash sous linux ou dans d'autres langues?

33
user121196

En supposant que vos paramètres régionaux soient définis sur UTF-8, cela fonctionne bien pour reconnaître les séquences UTF-8 non valides:

grep -axv '.*' file.txt
36
Blaf

Je voudrais grep pour les caractères non ASCII.

Avec GNU grep avec pcre (à cause de -P, pas toujours disponible. Sous FreeBSD, vous pouvez utiliser pcregrep dans le paquet pcre2) vous pouvez faire:

grep -P "[\x80-\xFF]" file

Référence dans Comment puis-je grep pour tous les caractères non-ASCII dans UNIX . Donc, en fait, si vous voulez seulement vérifier si le fichier contient des caractères non ASCII, vous pouvez simplement dire:

if grep -qP "[\x80-\xFF]" file ; then echo "file contains ascii"; fi
#        ^
#        silent grep

Pour supprimer ces caractères, vous pouvez utiliser:

sed -i.bak 's/[\d128-\d255]//g' file

Cela créera un fichier file.bak en tant que sauvegarde, tandis que la file d'origine aura ses caractères non ASCII supprimés. Référence dans Supprimez les caractères non-ascii de csv .

9
fedorqui

Ce que vous regardez est par définition corrompu. Apparemment, vous affichez le fichier tel qu’il est rendu en Latin-1; les trois caractères � représentent les trois valeurs d'octets 0xEF 0xBF 0xBD. Mais c’est le codage UTF-8 de Unicode REMPLACEMENT CHARACTER U + FFFD qui est le résultat de la tentative de conversion d’octets d’un codage inconnu ou indéfini en UTF-8 et qui s’afficherait correctement sous la forme (si vous avez un navigateur de ce siècle, vous devriez voir quelque chose comme un diamant noir avec un point d'interrogation, mais cela dépend aussi de la police que vous utilisez, etc.).

Votre question sur "comment détecter" ce phénomène est donc simple; le point de code Unicode U + FFFD est un cadeau mort et le seul symptôme possible du processus que vous indiquez.

Ce ne sont pas des "Unicodes invalides" ou des "UTF-8 invalides" en ce sens qu'il s'agit d'une séquence UTF-8 valide qui code un point de code Unicode valide; c'est juste que la sémantique de ce point de code particulier est "ceci est un caractère de remplacement pour un caractère qui ne pourrait pas être représenté correctement", c'est-à-dire une entrée invalide.

Pour ce qui est de la manière de la prévenir, la réponse est très simple, mais elle n’est pas non plus informative - vous devez identifier quand et comment un codage incorrect a eu lieu et réparer le processus qui a généré cette sortie invalide.

Pour supprimer simplement les caractères U + FFFD, essayez quelque chose comme

Perl -CSD -pe 's/\x{FFFD}//g' file

mais encore une fois, la bonne solution est de ne pas générer ces sorties erronées en premier lieu.

(Vous ne révélez pas le codage de vos données d'exemple. Il est possible qu'il soit corrompu supplémentaire. Si ce que vous nous montrez est un copier/coller du rendu UTF-8 des données, il a été "double-encodé". En d'autres termes, quelqu'un a pris - déjà corrompu, le texte UTF-8 ci-dessus et a demandé à l'ordinateur de le convertir du latin-1 au format UTF-8. "retour" en Latin 1. Ce que vous obtenez devrait alors être les données UTF-8 d'origine avant la conversion incorrecte superflue.)

4
tripleee

Ce programme Perl devrait supprimer tous les caractères non-ASCII:

 foreach $file (@ARGV) {
   open(IN, $file);
   open(OUT, "> super-temporary-utf8-replacement-file-which-should-never-be-used-EVER");
   while (<IN>) {
     s/[^[:ascii:]]//g;
     print OUT "$_";
   }
   rename "super-temporary-utf8-replacement-file-which-should-never-be-used-EVER", $file;
}

Cela prend des fichiers comme entrée sur la ligne de commande, comme ceci:
Perl fixutf8.pl foo bar baz
Ensuite, pour chaque ligne, il remplace chaque instance d'un caractère non-ASCII par rien (suppression).
Il écrit ensuite cette ligne modifiée dans super-temporary-utf8-replacement-file-which-should-never-be-used-EVER (nommé afin de ne modifier aucun autre fichier.)
Ensuite, il renomme le fichier temporaire en celui du fichier d'origine.

Ceci accepte TOUS les caractères ASCII (y compris DEL, NUL, CR, etc.), au cas où vous en auriez une utilisation spéciale. Si vous souhaitez uniquement des caractères imprimables, remplacez simplement :ascii: par :print: dans s///.

J'espère que ça aide! S'il vous plaît laissez-moi savoir si ce n'est pas ce que vous cherchiez.

3
ASCIIThenANSI

Je répète probablement ce que d'autres ont déjà dit. Mais je pense que vos caractères invalides sont toujours imprimés car ils sont peut-être valides. Le jeu de caractères universel est la tentative de référencer les caractères couramment utilisés dans le monde entier pour pouvoir écrire un logiciel robuste qui ne s'appuie pas sur un jeu de caractères spécial.

Donc, je pense que votre problème peut être l'un des deux suivants - en supposant que votre objectif général est de gérer cette entrée (malveillante) à partir de fichiers utf en général:

  1. Il y a invalid utf8 caractères (mieux appelé séquences d'octets invalides - pour cela, je voudrais faire référence au Wikipedia-Article correspondant.
  2. Il existe dans votre fonte d'affichage actuelle des équivalents absent qui sont remplacés par un symbole spécial ou représentés par leur équivalent binaire ASCII (fe - i voudrait donc se référer à la so-post suivante: UTF-8 les caractères spéciaux n'apparaissent pas ).

Donc, à mon avis, vous avez deux manières possibles de gérer cela:

  1. Transformez tous les caractères d'utf8 en quelque chose de manipulable - f.e. ASCII - cela peut être fait par exemple. avec iconv -f utf-8 -t ascii -o file_in_ascii.txt file_in_utf8.txt. Mais être prudent transférer d'un espace de caractères plus large (utf) à un espace plus petit pourrait entraîner une perte de données.
  2. Gère correctement utf (8) - c’est comme ça que le monde écrit. Si vous pensez devoir utiliser des caractères ASCII à cause d'une étape de post-traitement limitante, arrêtez-vous et repensez. Dans la plupart des cas, le post-processeur supporte déjà utf, il est probablement préférable de savoir comment l'utiliser. Vous préparez vos affaires à l'avenir et à l'épreuve des balles.

La manipulation de utf peut sembler délicate, les étapes suivantes peuvent vous aider à accomplir la tâche utf-readyness:

  • Être capable d’afficher correctement ou de s’assurer que votre pile d’affichage (os, terminal, etc.) est capable d’afficher un sous-ensemble adéquat d’Unicode (ce qui, bien sûr, devrait répondre à vos besoins), ce qui pourrait vous éviter d’avoir besoin d’un hex rédacteur dans de nombreux cas. Malheureusement, utf est trop gros pour être écrit dans une police, mais un bon point de départ est le suivant: https://stackoverflow.com/questions/586503/complete-monospaced-unicode-font
  • Être capable de filtrer les séquences d'octets non valides. Et il y a plusieurs façons de le faire, cet ul-post en présente une grande variété: Filtrage de l'utf8 invalide - je tiens à signaler en particulier la 4ème réponse suggérant d'utiliser uconv qui vous permet pour définir un gestionnaire de rappel pour les séquences non valides.
  • Lisez un peu plus sur Unicode.
1
florianb

Une solution très sale en python 3

import sys
with open ("cur.txt","r",encoding="utf-8") as f:
    for i in f:
            for c in i:
                 if(ord(c)<128):
                     print(c,end="")

Le résultat devrait être:

>two_o~}}w~_^s?w}yo}
1
xitij

Le programme C suivant détecte les caractères utf8 invalides . Il a été testé et utilisé sur un système Linux.

/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

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

void usage( void ) {
    printf( "Usage: test_utf8 file ...\n" );

    return;
}

int line_number = 1;
int char_number = 1;
char *file_name = NULL;

void inv_char( void ) {
    printf( "%s: line : %d - char %d\n", file_name, line_number, char_number );

    return;
}

int main( int argc, char *argv[]) {

    FILE *out = NULL;
    FILE *fh = NULL;

//    printf( "argc: %d\n", argc );

    if( argc < 2 ) {
        usage();
        exit( 1 );
    }

//    printf( "File: %s\n", argv[1] );

    file_name = argv[1];

    fh = fopen( file_name, "rb" );
    if( ! fh ) {
        printf( "Could not open file '%s'\n", file_name );
        exit( 1 );
    }

    int utf8_type = 1;
    int utf8_1 = 0;
    int utf8_2 = 0;
    int utf8_3 = 0;
    int utf8_4 = 0;
    int byte_count = 0;
    int expected_byte_count = 0;

    int cin = fgetc( fh );
    while( ! feof( fh ) ) {
        switch( utf8_type ) {
            case 1:
                if( (cin & 0x80) ) {
                    if( (cin & 0xe0) == 0xc0 ) {
                        utf8_1 = cin;
                        utf8_type = 2;
                        byte_count = 1;
                        expected_byte_count = 2;
                        break;
                    }

                    if( (cin & 0xf0) == 0xe0 ) {
                        utf8_1 = cin;
                        utf8_type = 2;
                        byte_count = 1;
                        expected_byte_count = 3;
                        break;
                    }

                    if( (cin & 0xf8) == 0xf0 ) {
                        utf8_1 = cin;
                        utf8_type = 2;
                        byte_count = 1;
                        expected_byte_count = 4;
                        break;
                    }

                    inv_char();
                    utf8_type = 1;
                    break;
                }

                break;

            case 2:
            case 3:
            case 4:
//                printf( "utf8_type - %d\n", utf8_type );
//                printf( "%c - %02x\n", cin, cin );
                if( (cin & 0xc0) == 0x80 ) {
                    if( utf8_type == expected_byte_count ) {
                        utf8_type = 1;
                        break;
                    }

                    byte_count = utf8_type;
                    utf8_type++;

                    if( utf8_type == 5 ) {
                        utf8_type = 1;
                    }

                    break;
                }

                inv_char();
                utf8_type = 1;
                break;

            default:
                inv_char();
                utf8_type = 1;
                break;
        }

        if( cin == '\n' ) {
            line_number ++;
            char_number = 0;
        }

        if( out != NULL ) {
            fputc( cin, out );
        }

//        printf( "lno: %d\n", line_number );

        cin = fgetc( fh );
        char_number++;
    }

    fclose( fh );

    return 0;
}
1
DBA

Essayez ceci, afin de trouver des caractères non-ASCII à partir du shell.

Commander:

$ Perl -ne 'print "$. $_" if m/[\x80-\xFF]/'  utf8.txt

Sortie:

2 Pour être ou ne pas être
4 Byť či nebyť
5 是或不
0
Bouramas