Geçersiz utf8 filtreleme


50

Bilinmeyen veya karışık kodlamada bir metin dosyasına sahibim. UTF-8 geçerli olmayan bir bayt dizisi içeren satırları görmek istiyorum (metin dosyasını bir programa aktararak). Eşdeğer olarak, geçerli UTF-8 olan satırları filtrelemek istiyorum. Başka bir deyişle, arıyorum .grep [notutf8]

İdeal bir çözüm taşınabilir, kısa ve diğer kodlamalara genelleştirilebilir olacaktır, ancak en iyi yolun UTF-8 tanımına göre pişirmek olduğunu düşünüyorsanız , devam edin.


Olası bir regex için ayrıca keithdevens.com/weblog/archive/2004/Jun/29/UTF-8.regex adresini ziyaret edin.
Mikel

@Mikel: ya da unix.stackexchange.com/questions/6460/…… Daha az beceriksiz bir şey umuyordum.
Gilles 'SO- kötülük'

Yanıtlar:


34

Kullanmak istiyorsanız grep, şunları yapabilirsiniz:

grep -axv '.*' file

UTF-8 yerellerinde, en azından geçersiz bir UTF-8 dizisine sahip olan çizgileri elde etmek için kullanılır (bu, en azından GNU Grep ile çalışır).


Bunun dışında -a, POSIX tarafından çalışmak için gerekli. Bununla birlikte GNU grep, en azından UTF-8 kodlu UTF-16 vekili olmayan karakterleri veya kod noktalarını 0x10FFFF'ın üzerinde belirleyemiyor.
Stéphane Chazelas,

1
@ StéphaneChazelas Tersine, -aGNU tarafından ihtiyaç duyulmaktadır grep(ki bu POSIX uyumlu değil, sanırım). İlgili olarak vekil alanı ve 0x10FFFF yukarıdaki codepoints, bu (açıklayabilir ki o zaman bir hata olduğunu o ). Bunun için ekleme -PGNU grep2.21 ile çalışması gerekir (ancak yavaş); en azından Debian grep / 2.20-4’de araba var .
vinc17

Üzgünüm, benim hatam, davranış POSIX'de belirtilmemiş çünkü grepbir metin aracı (sadece metin girişi üzerinde çalışması bekleniyor), GNU grep'in davranışının burada olduğu kadar geçerli olduğunu düşünüyorum.
Stéphane Chazelas

@ StéphaneChazelas POSIX'in şunları söylediğini onaylıyorum: "Giriş dosyaları metin dosyaları olacaktır." (açıklama bölümünde olmasa da, bu biraz yanıltıcıdır). Bu aynı zamanda geçersiz diziler durumunda, davranışın POSIX tarafından tanımlanmadığı anlamına gelir. Dolayısıyla, GNU grep(niyeti geçersiz dizileri eşleşmeyen olarak görmektir) ve olası hataları gibi uygulamayı bilme ihtiyacı .
vinc17

1
Özür, (bu bir kabul cevabı değiştiriyorum Peter.O çok basit ve diğer ortak kodlamaları (özellikle 8 bit kodlama) den UTF-8 ayırt etmek bir sezgisel olan birincil kullanım durumunda, için iyi çalışır çünkü. Stephane Chazelas ve Peter.O UTF-8 uyum açısından daha doğru yanıtlar vermeye.
Gilles 'kötü olarak durdurmak yani-'

33

Sanırım iconv'u istiyorsun . Kod setleri arasında dönüştürme yapmak için kullanılır ve çok fazla sayıda biçimi destekler. Örneğin, UTF-8'de geçerli olmayan bir şeyi silmek için kullanabilirsiniz:

iconv -c -t UTF-8 < input.txt > output.txt

-C seçeneği olmadan stderr'e dönüştürme problemlerini rapor eder, böylece proses yönü ile bunların bir listesini kaydedebilirsiniz. Başka bir yol da UTF8 dışı maddeleri soymak ve sonra

diff input.txt output.txt

değişikliklerin yapıldığı yerlerin listesi için.


Tamam bu iconv -c -t UTF-8 <input.txt | diff input.txt - | sed -ne 's/^< //p'. Bununla birlikte, bir boru hattı olarak çalışmayacaktır, çünkü girişi iki kez okumanız gerekir (hayır, teeyapmayacak, ne kadar tamponlamaya iconvve diffne yapacağına bağlı olarak engelleyebilir ).
Gilles 'SO- kötülük'

2
Rastgele not: giriş ve çıkış aynı dosya olmayabilir veya boş bir dosya ile
sonuçlanacaktır

1
Ya da kabuğunuz destekliyorsa işlem değiştirmeyi kullanındiff <(iconv -c -t UTF-8 <input.txt) input.txt
Karl

Bunu nasıl yaparsınız ve çıktıyı girdiyle aynı dosyaya yaparsınız. Sadece bu yaptım ve Boş bir dosya var iconv -c -t UTF-8 <girdi.txt> girdi.txt
Kostas Vrahimis

1
Teşekkürler .. Bu kırık utf-8 postgresql dökümü geri yükleme sağlar, ancak geçerli utf-8 atma
Superbiji

21

Düzenleme: regex içinde bir yazım hatası-düzelttim .. Bir \ \ 80 \ değil \ \ gerekli .

UTF-8'e sıkı sıkıya bağlı kalmak için geçersiz UTF-8 formlarını filtrelemek için regex aşağıdaki gibidir

perl -l -ne '/
 ^( ([\x00-\x7F])              # 1-byte pattern
   |([\xC2-\xDF][\x80-\xBF])   # 2-byte pattern
   |((([\xE0][\xA0-\xBF])|([\xED][\x80-\x9F])|([\xE1-\xEC\xEE-\xEF][\x80-\xBF]))([\x80-\xBF])) # 3-byte pattern
   |((([\xF0][\x90-\xBF])|([\xF1-\xF3][\x80-\xBF])|([\xF4][\x80-\x8F]))([\x80-\xBF]{2}))       # 4-byte pattern
  )*$ /x or print'

Çıktı ( Test satırından anahtar satırlarının ):

Codepoint
=========  
00001000  Test=1 mode=strict               valid,invalid,fail=(1000,0,0)          
0000E000  Test=1 mode=strict               valid,invalid,fail=(D800,800,0)          
0010FFFF  mode=strict  test-return=(0,0)   valid,invalid,fail=(10F800,800,0)          

S. geçersiz Unicode'u filtreleyen bir regex'i test etmek için test verileri nasıl oluşturulur?
A. Kendi UTF-8 test algoritmanızı oluşturun ve kurallarına
uyun ... Catch-22 .. Ama sonra test algoritmanızı nasıl test edersiniz?

Normal ifade, yukarıdaki (kullanılarak test edilmiştir iconvher tam sayı değeri için referans olarak) 0x00000için 0x10FFFFolan bu üst değere .. bir Unicode kodlaması maksimum sayı değeri

Bu wikipedia'ya göre UTF-8 .

  • UTF-8, bir ila dört 8 bit bayt kullanarak Unicode karakter kümesindeki 1.112.064 kod noktasının her birini kodlar

Bu numeber (1.112.064), bir dizi denk gelmektedir 0x000000için 0x10F7FFyüksek Unicode kodlaması için fiili maksimum tamsayı değer 0x0800 çekingen olan,:0x10FFFF

Bu tamsayılar bloğu , Unicode Kod Noktaları spektrumunda eksiktir, çünkü UTF-16 kodlamasının, vekil çiftleri adı verilen bir sistem aracılığıyla orijinal tasarım amacının ötesine geçmesi ihtiyacı nedeniyle . Bir blok tamsayılar Bu blok aralığını kapsayan .. UTF-16 tarafından kullanılmak üzere rezerve edilmiş etmek . Bu girişlerin hiçbiri yasal Unicode değerleri değildir ve bu nedenle geçersiz UTF-8 değerleridir. 0x08000x00D8000x00DFFF

In Testi 1 , regexUnicode codepoints aralığında her sayı karşı test ve bunun exectly sonuçlarını maçları olmuştur iconv .. yani. 0x010F7FF geçerli değerleri ve 0x000800 geçersiz değerleri.

Ancak, şu an ortaya çıkan sorun, * regex Aralık Dışında UTF-8 Değerini nasıl ele alıyor; yukarıda 0x010FFFF(UTF-8, maksimum 0x7FFFFFFF değerinde bir tamsayı değerine sahip 6 bayta kadar uzatabilir mi?
Gerekli * unicode olmayan UTF-8 bayt değerlerini oluşturmak için aşağıdaki komutu kullandım:

  perl -C -e 'print chr 0x'$hexUTF32BE

Geçerliliklerini test etmek için (bir şekilde) Gilles'UTF-8 regex kullandım ...

  perl -l -ne '/
   ^( [\000-\177]                 # 1-byte pattern
     |[\300-\337][\200-\277]      # 2-byte pattern
     |[\340-\357][\200-\277]{2}   # 3-byte pattern
     |[\360-\367][\200-\277]{3}   # 4-byte pattern
     |[\370-\373][\200-\277]{4}   # 5-byte pattern
     |[\374-\375][\200-\277]{5}   # 6-byte pattern
    )*$ /x or print'

'Perl baskı chr' çıkışı kullanamıyorum .. Bir diğer geçerliliğini güçlendirir .. Gilles' regex filtrelemeyi maçları iconvsadece daha geniş (orijinal) UTF-8'in geçerli Unicode Standardı alt kümesini işlemesi nedeniyle standart...

Katılan rahibeler oldukça geniş, bu yüzden üst seviye, üst seviye ve birkaç tarama adımlarını adım adım 11111, 13579, 33333, 53441 gibi test ettik ... Sonuçların hepsi eşleşiyor. Geriye kalan tek şey, regex'i bu sınıf dışı UTF-8 tarzı değerlere karşı sınamaktır (Unicode için geçersiz ve bu nedenle katı UTF-8'in kendisi için geçersiz).


İşte test modülleri:

[[ "$(locale charmap)" != "UTF-8" ]] && { echo "ERROR: locale must be UTF-8, but it is $(locale charmap)"; exit 1; }

# Testing the UTF-8 regex
#
# Tests to check that the observed byte-ranges (above) have
#  been  accurately observed and included in the test code and final regex. 
# =========================================================================
: 2 bytes; B2=0 #  run-test=1   do-not-test=0
: 3 bytes; B3=0 #  run-test=1   do-not-test=0
: 4 bytes; B4=0 #  run-test=1   do-not-test=0 

:   regex; Rx=1 #  run-test=1   do-not-test=0

           ((strict=16)); mode[$strict]=strict # iconv -f UTF-16BE  then iconv -f UTF-32BE beyond 0xFFFF)
           ((   lax=32)); mode[$lax]=lax       # iconv -f UTF-32BE  only)

          # modebits=$strict
                  # UTF-8, in relation to UTF-16 has invalid values
                  # modebits=$strict automatically shifts to modebits=$lax
                  # when the tested integer exceeds 0xFFFF
          # modebits=$lax 
                  # UTF-8, in relation to UTF-32, has no restrictione


           # Test 1 Sequentially tests a range of Big-Endian integers
           #      * Unicode Codepoints are a subset ofBig-Endian integers            
           #        ( based on 'iconv' -f UTF-32BE -f UTF-8 )    
           # Note: strict UTF-8 has a few quirks because of UTF-16
                    #    Set modebits=16 to "strictly" test the low range

             Test=1; modebits=$strict
           # Test=2; modebits=$lax
           # Test=3
              mode3wlo=$(( 1*4)) # minimum chars * 4 ( '4' is for UTF-32BE )
              mode3whi=$((10*4)) # minimum chars * 4 ( '4' is for UTF-32BE )


#########################################################################  

# 1 byte  UTF-8 values: Nothing to do; no complexities.

#########################################################################

#  2 Byte  UTF-8 values:  Verifying that I've got the right range values.
if ((B2==1)) ; then  
  echo "# Test 2 bytes for Valid UTF-8 values: ie. values which are in range"
  # =========================================================================
  time \
  for d1 in {194..223} ;do
      #     bin       oct  hex  dec
      # lo  11000010  302   C2  194
      # hi  11011111  337   DF  223
      B2b1=$(printf "%0.2X" $d1)
      #
      for d2 in {128..191} ;do
          #     bin       oct  hex  dec
          # lo  10000000  200   80  128
          # hi  10111111  277   BF  191
          B2b2=$(printf "%0.2X" $d2)
          #
          echo -n "${B2b1}${B2b2}" |
            xxd -p -u -r  |
              iconv -f UTF-8 >/dev/null || { 
                echo "ERROR: Invalid UTF-8 found: ${B2b1}${B2b2}"; exit 20; }
          #
      done
  done
  echo

  # Now do a negated test.. This takes longer, because there are more values.
  echo "# Test 2 bytes for Invalid values: ie. values which are out of range"
  # =========================================================================
  # Note: 'iconv' will treat a leading  \x00-\x7F as a valid leading single,
  #   so this negated test primes the first UTF-8 byte with values starting at \x80
  time \
  for d1 in {128..193} {224..255} ;do 
 #for d1 in {128..194} {224..255} ;do # force a valid UTF-8 (needs $B2b2) 
      B2b1=$(printf "%0.2X" $d1)
      #
      for d2 in {0..127} {192..255} ;do
     #for d2 in {0..128} {192..255} ;do # force a valid UTF-8 (needs $B2b1)
          B2b2=$(printf "%0.2X" $d2)
          #
          echo -n "${B2b1}${B2b2}" |
            xxd -p -u -r |
              iconv -f UTF-8 2>/dev/null && { 
                echo "ERROR: VALID UTF-8 found: ${B2b1}${B2b2}"; exit 21; }
          #
      done
  done
  echo
fi

#########################################################################

#  3 Byte  UTF-8 values:  Verifying that I've got the right range values.
if ((B3==1)) ; then  
  echo "# Test 3 bytes for Valid UTF-8 values: ie. values which are in range"
  # ========================================================================
  time \
  for d1 in {224..239} ;do
      #     bin       oct  hex  dec
      # lo  11100000  340   E0  224
      # hi  11101111  357   EF  239
      B3b1=$(printf "%0.2X" $d1)
      #
      if   [[ $B3b1 == "E0" ]] ; then
          B3b2range="$(echo {160..191})"
          #     bin       oct  hex  dec  
          # lo  10100000  240   A0  160  
          # hi  10111111  277   BF  191
      elif [[ $B3b1 == "ED" ]] ; then
          B3b2range="$(echo {128..159})"
          #     bin       oct  hex  dec  
          # lo  10000000  200   80  128  
          # hi  10011111  237   9F  159
      else
          B3b2range="$(echo {128..191})"
          #     bin       oct  hex  dec
          # lo  10000000  200   80  128
          # hi  10111111  277   BF  191
      fi
      # 
      for d2 in $B3b2range ;do
          B3b2=$(printf "%0.2X" $d2)
          echo "${B3b1} ${B3b2} xx"
          #
          for d3 in {128..191} ;do
              #     bin       oct  hex  dec
              # lo  10000000  200   80  128
              # hi  10111111  277   BF  191
              B3b3=$(printf "%0.2X" $d3)
              #
              echo -n "${B3b1}${B3b2}${B3b3}" |
                xxd -p -u -r  |
                  iconv -f UTF-8 >/dev/null || { 
                    echo "ERROR: Invalid UTF-8 found: ${B3b1}${B3b2}${B3b3}"; exit 30; }
              #
          done
      done
  done
  echo

  # Now do a negated test.. This takes longer, because there are more values.
  echo "# Test 3 bytes for Invalid values: ie. values which are out of range"
  # =========================================================================
  # Note: 'iconv' will treat a leading  \x00-\x7F as a valid leading single,
  #   so this negated test primes the first UTF-8 byte with values starting at \x80
  #
  # real     26m28.462s \ 
  # user     27m12.526s  | stepping by 2
  # sys      13m11.193s /
  #
  # real    239m00.836s \
  # user    225m11.108s  | stepping by 1
  # sys     120m00.538s /
  #
  time \
  for d1 in {128..223..1} {240..255..1} ;do 
 #for d1 in {128..224..1} {239..255..1} ;do # force a valid UTF-8 (needs $B2b2,$B3b3) 
      B3b1=$(printf "%0.2X" $d1)
      #
      if   [[ $B3b1 == "E0" ]] ; then
          B3b2range="$(echo {0..159..1} {192..255..1})"
         #B3b2range="$(> {192..255..1})" # force a valid UTF-8 (needs $B3b1,$B3b3)
      elif [[ $B3b1 == "ED" ]] ; then
          B3b2range="$(echo {0..127..1} {160..255..1})"
         #B3b2range="$(echo {0..128..1} {160..255..1})" # force a valid UTF-8 (needs $B3b1,$B3b3)
      else
          B3b2range="$(echo {0..127..1} {192..255..1})"
         #B3b2range="$(echo {0..128..1} {192..255..1})" # force a valid UTF-8 (needs $B3b1,$B3b3)
      fi
      for d2 in $B3b2range ;do
          B3b2=$(printf "%0.2X" $d2)
          echo "${B3b1} ${B3b2} xx"
          #
          for d3 in {0..127..1} {192..255..1} ;do
         #for d3 in {0..128..1} {192..255..1} ;do # force a valid UTF-8 (needs $B2b1)
              B3b3=$(printf "%0.2X" $d3)
              #
              echo -n "${B3b1}${B3b2}${B3b3}" |
                xxd -p -u -r |
                  iconv -f UTF-8 2>/dev/null && { 
                    echo "ERROR: VALID UTF-8 found: ${B3b1}${B3b2}${B3b3}"; exit 31; }
              #
          done
      done
  done
  echo

fi

#########################################################################

#  Brute force testing in the Astral Plane will take a VERY LONG time..
#  Perhaps selective testing is more appropriate, now that the previous tests 
#     have panned out okay... 
#  
#  4 Byte  UTF-8 values:
if ((B4==1)) ; then  
  echo "# Test 4 bytes for Valid UTF-8 values: ie. values which are in range"
  # ==================================================================
  # real    58m18.531s \
  # user    56m44.317s  | 
  # sys     27m29.867s /
  time \
  for d1 in {240..244} ;do
      #     bin       oct  hex  dec
      # lo  11110000  360   F0  240
      # hi  11110100  364   F4  244  -- F4 encodes some values greater than 0x10FFFF;
      #                                    such a sequence is invalid.
      B4b1=$(printf "%0.2X" $d1)
      #
      if   [[ $B4b1 == "F0" ]] ; then
        B4b2range="$(echo {144..191})" ## f0 90 80 80  to  f0 bf bf bf
        #     bin       oct  hex  dec          010000  --  03FFFF 
        # lo  10010000  220   90  144  
        # hi  10111111  277   BF  191
        #                            
      elif [[ $B4b1 == "F4" ]] ; then
        B4b2range="$(echo {128..143})" ## f4 80 80 80  to  f4 8f bf bf
        #     bin       oct  hex  dec          100000  --  10FFFF 
        # lo  10000000  200   80  128  
        # hi  10001111  217   8F  143  -- F4 encodes some values greater than 0x10FFFF;
        #                                    such a sequence is invalid.
      else
        B4b2range="$(echo {128..191})" ## fx 80 80 80  to  f3 bf bf bf
        #     bin       oct  hex  dec          0C0000  --  0FFFFF
        # lo  10000000  200   80  128          0A0000
        # hi  10111111  277   BF  191
      fi
      #
      for d2 in $B4b2range ;do
          B4b2=$(printf "%0.2X" $d2)
          #
          for d3 in {128..191} ;do
              #     bin       oct  hex  dec
              # lo  10000000  200   80  128
              # hi  10111111  277   BF  191
              B4b3=$(printf "%0.2X" $d3)
              echo "${B4b1} ${B4b2} ${B4b3} xx"
              #
              for d4 in {128..191} ;do
                  #     bin       oct  hex  dec
                  # lo  10000000  200   80  128
                  # hi  10111111  277   BF  191
                  B4b4=$(printf "%0.2X" $d4)
                  #
                  echo -n "${B4b1}${B4b2}${B4b3}${B4b4}" |
                    xxd -p -u -r  |
                      iconv -f UTF-8 >/dev/null || { 
                        echo "ERROR: Invalid UTF-8 found: ${B4b1}${B4b2}${B4b3}${B4b4}"; exit 40; }
                  #
              done
          done
      done
  done
  echo "# Test 4 bytes for Valid UTF-8 values: END"
  echo
fi

########################################################################
# There is no test (yet) for negated range values in the astral plane. #  
#                           (all negated range values must be invalid) #
#  I won't bother; This was mainly for me to ge the general feel of    #     
#   the tests, and the final test below should flush anything out..    #
# Traversing the intire UTF-8 range takes quite a while...             #
#   so no need to do it twice (albeit in a slightly different manner)  #
########################################################################

################################
### The construction of:    ####
###  The Regular Expression ####
###      (de-construction?) ####
################################

#     BYTE 1                BYTE 2       BYTE 3      BYTE 4 
# 1: [\x00-\x7F]
#    ===========
#    ([\x00-\x7F])
#
# 2: [\xC2-\xDF]           [\x80-\xBF]
#    =================================
#    ([\xC2-\xDF][\x80-\xBF])
# 
# 3: [\xE0]                [\xA0-\xBF]  [\x80-\xBF]   
#    [\xED]                [\x80-\x9F]  [\x80-\xBF]
#    [\xE1-\xEC\xEE-\xEF]  [\x80-\xBF]  [\x80-\xBF]
#    ==============================================
#    ((([\xE0][\xA0-\xBF])|([\xED][\x80-\x9F])|([\xE1-\xEC\xEE-\xEF][\x80-\xBF]))([\x80-\xBF]))
#
# 4  [\xF0]                [\x90-\xBF]  [\x80-\xBF]  [\x80-\xBF]    
#    [\xF1-\xF3]           [\x80-\xBF]  [\x80-\xBF]  [\x80-\xBF]
#    [\xF4]                [\x80-\x8F]  [\x80-\xBF]  [\x80-\xBF]
#    ===========================================================
#    ((([\xF0][\x90-\xBF])|([\xF1-\xF3][\x80-\xBF])|([\xF4][\x80-\x8F]))([\x80-\xBF]{2}))
#
# The final regex
# ===============
# 1-4:  (([\x00-\x7F])|([\xC2-\xDF][\x80-\xBF])|((([\xE0][\xA0-\xBF])|([\xED][\x80-\x9F])|([\xE1-\xEC\xEE-\xEF][\x80-\xBF]))([\x80-\xBF]))|((([\xF0][\x90-\xBF])|([\xF1-\xF3][\x80-\xBF])|([\xF4][\x80-\x8F]))([\x80-\xBF]{2})))
# 4-1:  (((([\xF0][\x90-\xBF])|([\xF1-\xF3][\x80-\xBF])|([\xF4][\x80-\x8F]))([\x80-\xBF]{2}))|((([\xE0][\xA0-\xBF])|([\xED][\x80-\x9F])|([\xE1-\xEC\xEE-\xEF][\x80-\xBF]))([\x80-\xBF]))|([\xC2-\xDF][\x80-\xBF])|([\x00-\x7F]))


#######################################################################
#  The final Test; for a single character (multi chars to follow)     #  
#   Compare the return code of 'iconv' against the 'regex'            #
#   for the full range of 0x000000 to 0x10FFFF                        #
#                                                                     #     
#  Note; this script has 3 modes:                                     #
#        Run this test TWICE, set each mode Manually!                 #     
#                                                                     #     
#     1. Sequentially test every value from 0x000000 to 0x10FFFF      #     
#     2. Throw a spanner into the works! Force random byte patterns   #     
#     2. Throw a spanner into the works! Force random longer strings  #     
#        ==============================                               #     
#                                                                     #     
#  Note: The purpose of this routine is to determine if there is any  #
#        difference how 'iconv' and 'regex' handle the same data      #  
#                                                                     #     
#######################################################################
if ((Rx==1)) ; then
  # real    191m34.826s
  # user    158m24.114s
  # sys      83m10.676s
  time { 
  invalCt=0
  validCt=0
   failCt=0
  decBeg=$((0x00110000)) # incement by decimal integer
  decMax=$((0x7FFFFFFF)) # incement by decimal integer
  # 
  for ((CPDec=decBeg;CPDec<=decMax;CPDec+=13247)) ;do
      ((D==1)) && echo "=========================================================="
      #
      # Convert decimal integer '$CPDec' to Hex-digits; 6-long  (dec2hex)
      hexUTF32BE=$(printf '%0.8X\n' $CPDec)  # hexUTF32BE

      # progress count  
      if (((CPDec%$((0x1000)))==0)) ;then
          ((Test>2)) && echo
          echo "$hexUTF32BE  Test=$Test mode=${mode[$modebits]}            "
      fi
      if   ((Test==1 || Test==2 ))
      then # Test 1. Sequentially test every value from 0x000000 to 0x10FFFF
          #
          if   ((Test==2)) ; then
              bits=32
              UTF8="$( perl -C -e 'print chr 0x'$hexUTF32BE |
                perl -l -ne '/^(  [\000-\177]
                                | [\300-\337][\200-\277]
                                | [\340-\357][\200-\277]{2}
                                | [\360-\367][\200-\277]{3}
                                | [\370-\373][\200-\277]{4}
                                | [\374-\375][\200-\277]{5}
                               )*$/x and print' |xxd -p )"
              UTF8="${UTF8%0a}"
              [[ -n "$UTF8" ]] \
                    && rcIco32=0 || rcIco32=1
                       rcIco16=

          elif ((modebits==strict && CPDec<=$((0xFFFF)))) ;then
              bits=16
              UTF8="$( echo -n "${hexUTF32BE:4}" |
                xxd -p -u -r |
                  iconv -f UTF-16BE -t UTF-8 2>/dev/null)" \
                    && rcIco16=0 || rcIco16=1  
                       rcIco32=
          else
              bits=32
              UTF8="$( echo -n "$hexUTF32BE" |
                xxd -p -u -r |
                  iconv -f UTF-32BE -t UTF-8 2>/dev/null)" \
                    && rcIco32=0 || rcIco32=1
                       rcIco16=
          fi
          # echo "1 mode=${mode[$modebits]}-$bits  rcIconv: (${rcIco16},${rcIco32})  $hexUTF32BE "
          #
          #
          #
          if ((${rcIco16}${rcIco32}!=0)) ;then
              # 'iconv -f UTF-16BE' failed produce a reliable UTF-8
              if ((bits==16)) ;then
                  ((D==1)) &&           echo "bits-$bits rcIconv: error    $hexUTF32BE .. 'strict' failed, now trying 'lax'"
                  #  iconv failed to create a  'srict' UTF-8 so   
                  #      try UTF-32BE to get a   'lax' UTF-8 pattern    
                  UTF8="$( echo -n "$hexUTF32BE" |
                    xxd -p -u -r |
                      iconv -f UTF-32BE -t UTF-8 2>/dev/null)" \
                        && rcIco32=0 || rcIco32=1
                  #echo "2 mode=${mode[$modebits]}-$bits  rcIconv: (${rcIco16},${rcIco32})  $hexUTF32BE "
                  if ((rcIco32!=0)) ;then
                      ((D==1)) &&               echo -n "bits-$bits rcIconv: Cannot gen UTF-8 for: $hexUTF32BE"
                      rcIco32=1
                  fi
              fi
          fi
          # echo "3 mode=${mode[$modebits]}-$bits  rcIconv: (${rcIco16},${rcIco32})  $hexUTF32BE "
          #
          #
          #
          if ((rcIco16==0 || rcIco32==0)) ;then
              # 'strict(16)' OR 'lax(32)'... 'iconv' managed to generate a UTF-8 pattern  
                  ((D==1)) &&       echo -n "bits-$bits rcIconv: pattern* $hexUTF32BE"
                  ((D==1)) &&       if [[ $bits == "16" && $rcIco32 == "0" ]] ;then 
                  echo " .. 'lax' UTF-8 produced a pattern"
              else
                  echo
              fi
               # regex test
              if ((modebits==strict)) ;then
                 #rxOut="$(echo -n "$UTF8" |perl -l -ne '/^(([\x00-\x7F])|([\xC2-\xDF][\x80-\xBF])|((([\xE0][\xA0-\xBF])|([\xED][\x80-\x9F])|([\xE1-\xEC\xEE-\xEF][\x80-\xBF]))([\x80-\xBF]))|((([\xF0][\x90-\xBF])|([\xF1-\xF3][\x80-\xBF])|([\xF4][\x80-\x8F]))([\x80-\xBF]{2})))*$/ or print' )"
                                     rxOut="$(echo -n "$UTF8" |
                  perl -l -ne '/^( ([\x00-\x7F])             # 1-byte pattern
                                  |([\xC2-\xDF][\x80-\xBF])  # 2-byte pattern
                                  |((([\xE0][\xA0-\xBF])|([\xED][\x80-\x9F])|([\xE1-\xEC\xEE-\xEF][\x80-\xBF]))([\x80-\xBF]))  # 3-byte pattern
                                  |((([\xF0][\x90-\xBF])|([\xF1-\xF3][\x80-\xBF])|([\xF4][\x80-\x8F]))([\x80-\xBF]{2}))        # 4-byte pattern
                                 )*$ /x or print' )"
               else
                  if ((Test==2)) ;then
                      rx="$(echo -n "$UTF8" |perl -l -ne '/^([\000-\177]|[\300-\337][\200-\277]|[\340-\357][\200-\277]{2}|[\360-\367][\200-\277]{3}|[\370-\373][\200-\277]{4}|[\374-\375][\200-\277]{5})*$/ and print')"
                      [[ "$UTF8" != "$rx" ]] && rxOut="$UTF8" || rxOut=
                      rx="$(echo -n "$rx" |sed -e "s/\(..\)/\1 /g")"  
                  else 
                      rxOut="$(echo -n "$UTF8" |perl -l -ne '/^([\000-\177]|[\300-\337][\200-\277]|[\340-\357][\200-\277]{2}|[\360-\367][\200-\277]{3}|[\370-\373][\200-\277]{4}|[\374-\375][\200-\277]{5})*$/ or print' )"
                  fi
              fi
              if [[ "$rxOut" == "" ]] ;then
                ((D==1)) &&           echo "        rcRegex: ok"
                  rcRegex=0
              else
                  ((D==1)) &&           echo -n "bits-$bits rcRegex: error    $hexUTF32BE .. 'strict' failed,"
                  ((D==1)) &&           if [[  "12" == *$Test* ]] ;then 
                                            echo # "  (codepoint) Test $Test" 
                                        else
                                            echo
                                        fi
                  rcRegex=1
              fi
          fi
          #
      elif [[ $Test == 2 ]]
      then # Test 2. Throw a randomizing spanner into the works! 
          #          Then test the  arbitary bytes ASIS
          #
          hexLineRand="$(echo -n "$hexUTF32BE" |
            sed -re "s/(.)(.)(.)(.)(.)(.)(.)(.)/\1\n\2\n\3\n\4\n\5\n\6\n\7\n\8/" |
              sort -R |
                tr -d '\n')"
          # 
      elif [[ $Test == 3 ]]
      then # Test 3. Test single UTF-16BE bytes in the range 0x00000000 to 0x7FFFFFFF
          #
          echo "Test 3 is not properly implemented yet.. Exiting"
          exit 99 
      else
          echo "ERROR: Invalid mode"
          exit
      fi
      #
      #
      if ((Test==1 || Test=2)) ;then
          if ((modebits==strict && CPDec<=$((0xFFFF)))) ;then
              ((rcIconv=rcIco16))
          else
              ((rcIconv=rcIco32))
          fi
          if ((rcRegex!=rcIconv)) ;then
              [[ $Test != 1 ]] && echo
              if ((rcRegex==1)) ;then
                  echo "ERROR: 'regex' ok, but NOT 'iconv': ${hexUTF32BE} "
              else
                  echo "ERROR: 'iconv' ok, but NOT 'regex': ${hexUTF32BE} "
              fi
              ((failCt++));
          elif ((rcRegex!=0)) ;then
            # ((invalCt++)); echo -ne "$hexUTF32BE  exit-codes $${rcIco16}${rcIco32}=,$rcRegex\t: $(printf "%0.8X\n" $invalCt)\t$hexLine$(printf "%$(((mode3whi*2)-${#hexLine}))s")\r"
              ((invalCt++)) 
          else
              ((validCt++)) 
          fi
          if   ((Test==1)) ;then
              echo -ne "$hexUTF32BE "    "mode=${mode[$modebits]}  test-return=($rcIconv,$rcRegex)   valid,invalid,fail=($(printf "%X" $validCt),$(printf "%X" $invalCt),$(printf "%X" $failCt))          \r"
          else 
              echo -ne "$hexUTF32BE $rx mode=${mode[$modebits]} test-return=($rcIconv,$rcRegex)  val,inval,fail=($(printf "%X" $validCt),$(printf "%X" $invalCt),$(printf "%X" $failCt))\r"
          fi
      fi
  done
  } # End time
fi
exit

Regexp ile ilgili asıl sorun, bazı yasaklı dizilere izin vermesidir \300\200(gerçekten kötü: 0 kod noktası boş bir bayt ile ifade edilmedi!). Bence senin regexp onları doğru reddediyor.
Gilles 'SO- kötülük'

7

UTF-8 verilerini incelemek için uconv( icu-devtoolsDebian paketinde) faydalı buluyorum :

$ print '\\xE9 \xe9 \u20ac \ud800\udc00 \U110000' |
    uconv --callback escape-c -t us
\xE9 \xE9 \u20ac \xED\xA0\x80\xED\xB0\x80 \xF4\x90\x80\x80

( \xS geçersiz karakterleri tespit etmeye yardımcı olur ( \xE9yukarıdaki hazır bilgi ile gönüllü olarak tanıtılan yanlış pozitif hariç )).

(diğer güzel kullanımların bol).


recodeBenzer şekilde kullanılabileceğini düşünüyorum - geçersiz bir çok baytlı diziyi çevirmek istenirse başarısız olması gerektiğini düşünüyorum . Emin değilim ama; onun için başarısız olmayacak print...|recode u8..u8/x4mesela (sadece yukarıda yaptığımız gibi bir HexDump yapar) o hiçbir şey yapmaz çünkü iconv data data, ama başarısız oluyor gibi recode u8..u2..u8/x4bundan sonra baskılar çevirir çünkü. Fakat bundan emin olmak için yeterince bilgim yok - ve birçok imkan var.
mikeserv

Eğer bir dosyam varsa, söyle test.txt. Çözümünüzü kullanarak geçersiz karakteri nasıl bulabilirim? usKodunuzda ne anlama geliyor?
jdhao

@Hao, usAmerika Birleşik Devletleri, yani ASCII için kısa. Girişi ASCII olmayan karakterlerin \uXXXXnotasyona ve karakter olmayan karaktere dönüştürüldüğü bir ASCII'ye dönüştürür \xXX.
Stéphane Chazelas

Komut dosyanızı kullanmak için dosyamı nereye koymalıyım? Koddaki son satır kodunuzun çıktısını engelliyor mu? Bu benim için biraz kafa karıştırıcı.
jdhao

4

Python'un 2.0 sürümünden beri yerleşikunicode bir işlevi vardır .

#!/usr/bin/env python2
import sys
for line in sys.stdin:
    try:
        unicode(line, 'utf-8')
    except UnicodeDecodeError:
        sys.stdout.write(line)

Python 3'te, unicodeiçine katlanmıştır str. Standart tanımlayıcılar için altta yatan nesneler olarak bayt benzeri bir nesnenin iletilmesi gerekir .buffer

#!/usr/bin/env python3
import sys
for line in sys.stdin.buffer:
    try:
        str(line, 'utf-8')
    except UnicodeDecodeError:
        sys.stdout.buffer.write(line)

python 2Bir (en az 2.7.6) ile bayrak UTF-8 UTF-16 vekil olmayan karakter başarısız olur.
Stéphane Chazelas

@ StéphaneChazelas Dammit. Teşekkürler. Sadece şu ana kadar nominal testleri yaptım, daha sonra Peter'ın test bataryasını çalıştıracağım.
Gilles 'SO- kötülük'

1

Benzer bir sorunla karşılaştım ("İçerik" bölümündeki detay) ve aşağıdaki ftfy_line_by_line.py çözümüyle karşılaştım:

#!/usr/bin/env python3
import ftfy, sys
with open(sys.argv[1], mode='rt', encoding='utf8', errors='replace') as f:
  for line in f:
    sys.stdout.buffer.write(ftfy.fix_text(line).encode('utf8', 'replace'))
    #print(ftfy.fix_text(line).rstrip().decode(encoding="utf-8", errors="replace"))

Mojibake ve diğer düzeltmeleri otomatik olarak düzeltmek için encode + change + ftfy komutunu kullanın .

bağlam

Temelde çalışan gen_basic_files_metadata.csv.sh betiğini kullanarak, temel dosya sistemi meta verilerinin> 10GiB CSV'sini topladım :

find "${path}" -type f -exec stat --format="%i,%Y,%s,${hostname},%m,%n" "{}" \;

Sorun ben birlikteydim dosya adları tutarsız kodlama neden dosya sistemleri arasında UnicodeDecodeError(piton uygulamalarla daha da işlerken csvsql daha spesifik olması).

Bu yüzden ftfy betiğinin üstüne başvurdum ve

Lütfen ftfy'nin oldukça yavaş olduğunu unutmayın ;

real    147m35.182s
user    146m14.329s
sys     2m8.713s

Karşılaştırma için sha256sum iken:

real    6m28.897s
user    1m9.273s
sys     0m6.210s

Intel (R) Çekirdek (TM) i7-3520M İşlemci @ 2.90GHz + 16GiB RAM'de (ve harici sürücüdeki veriler)


Ve evet, bu find komutunun csv standardına göre tırnak içeren dosya adlarını doğru bir şekilde kodlamayacağını biliyorum
Grzegorz Wierzowiecki
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.