it-swarm-vi.com

Lọc utf8 không hợp lệ

Tôi có một tệp văn bản trong một mã hóa không xác định hoặc hỗn hợp. Tôi muốn xem các dòng chứa một chuỗi byte không hợp lệ UTF-8 (bằng cách chuyển tệp văn bản vào một số chương trình). Tương tự, tôi muốn lọc ra các dòng UTF-8 hợp lệ. Nói cách khác, tôi đang tìm kiếm grep [notutf8].

Một giải pháp lý tưởng sẽ là di động, ngắn gọn và có thể khái quát hóa cho các bảng mã khác, nhưng nếu bạn cảm thấy cách tốt nhất là nướng trong định nghĩa của UTF-8 , hãy tiếp tục.

Nếu bạn muốn sử dụng grep, bạn có thể làm:

grep -axv '.*' file

trong các vị trí UTF-8 để có được các dòng có ít nhất một chuỗi UTF-8 không hợp lệ (điều này hoạt động với GNU Grep ít nhất).

36
vinc17

Tôi nghĩ bạn có thể muốn iconv . Đó là để chuyển đổi giữa các bộ mã và hỗ trợ một số định dạng vô lý. Ví dụ: để loại bỏ mọi thứ không hợp lệ trong UTF-8, bạn có thể sử dụng:

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

Nếu không có tùy chọn -c, nó sẽ báo cáo các vấn đề khi chuyển đổi thành stderr, vì vậy với hướng xử lý, bạn có thể lưu danh sách các mục này. Một cách khác là loại bỏ những thứ không phải UTF8 và sau đó

diff input.txt output.txt

cho một danh sách nơi thay đổi đã được thực hiện.

33
frabjous

Chỉnh sửa: Tôi đã sửa lỗi chính tả trong regex .. Nó cần một '\ x80` chứ không phải \80.

Regex để lọc các biểu mẫu UTF-8 không hợp lệ, cho tuân thủ nghiêm ngặt với UTF-8, như sau

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'

Đầu ra (của các dòng chính. Từ Kiểm tra 1):

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)          

H: Làm thế nào để người ta tạo dữ liệu thử nghiệm để kiểm tra regex lọc bộ lọc Unicode không hợp lệ?
[.__.] A. Tạo thuật toán thử nghiệm UTF-8 của riêng bạn và phá vỡ quy tắc của nó ...
[.__.] Catch-22 .. Nhưng sau đó, làm thế nào để bạn kiểm tra thuật toán thử nghiệm của mình?

Regex, ở trên, đã được thử nghiệm (sử dụng iconv làm tham chiếu) cho mọi giá trị số nguyên từ 0x00000 đến 0x10FFFF .. Giá trị trên này là giá trị số nguyên tối đa của Codepoint Unicode

Theo điều này wikipedia UTF-8 trang ,.

  • UTF-8 mã hóa từng trong số 1.112.064 điểm mã trong bộ ký tự Unicode, sử dụng một đến bốn byte 8 bit

Con số này (1.112.064) tương đương với một phạm vi 0x000000 đến 0x10F7FF, tức là 0x0800 so với giá trị số nguyên tối đa thực tế cho Codepoint Unicode cao nhất: 0x10FFFF

Khối số nguyênnày bị thiếu trong phổ Unicode Codepoints, do nhu cầu mã hóa UTF-16 đến vượt quá mục đích thiết kế ban đầu của nóthông qua một hệ thống gọi là cặp thay thế . Một khối 0x0800 số nguyên đã được dành riêng để sử dụng bởi UTF-16 .. Khối này mở rộng phạm vi0x00D800 đến 0x00DFFF. Không có thông số nào trong số này là giá trị Unicode hợp pháp và do đó là giá trị UTF-8 không hợp lệ.

Trong Kiểm tra 1 , regex đã được kiểm tra đối với mọi số trong phạm vi Mã hóa Unicode và phù hợp với kết quả của iconv .. tức là. 0x010F7FFgiá trị hợp lệ và 0x000800giá trị không hợp lệ.

Tuy nhiên, vấn đề bây giờ phát sinh là, * Làm thế nào để regex xử lý Giá trị UTF-8 ngoài phạm vi; ở trên 0x010FFFF (UTF-8 có thể mở rộng đến 6 byte, với giá trị nguyên tối đa là x7FFFFFFF ?
[.___.] Để tạo các giá trị cần thiết * không phải unicode UTF-8 byte, tôi đã sử dụng lệnh sau:

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

Để kiểm tra tính hợp lệ của chúng (trong một số thời trang), tôi đã sử dụng Gilles' Regex UTF-8 ...

  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'

Đầu ra của 'Perl's print chr' khớp với bộ lọc của biểu thức Gilles '.. Người ta củng cố tính hợp lệ của người khác .. Tôi không thể sử dụng iconv vì nó chỉ xử lý tập hợp con Chuẩn Unicode hợp lệ của rộng hơn (bản gốc) tiêu chuẩn UTF-8 ...

Các nữ tu có liên quan khá lớn, vì vậy tôi đã thử nghiệm các phạm vi hàng đầu, dưới cùng của phạm vi và một số lần quét theo từng bước như, 11111, 13579, 33333, 53441 ... Các kết quả đều khớp tất cả những gì còn lại là để kiểm tra regex dựa trên các giá trị kiểu UTF-8 ngoài phạm vi này (không hợp lệ đối với Unicode, và do đó cũng không hợp lệ đối với chính UTF-8 nghiêm ngặt) ..


Dưới đây là các mô-đun thử nghiệm:

[[ "$(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
21
Peter.O

Tôi tìm thấy uconv (trong icu-devtools gói trong Debian) hữu ích để kiểm tra dữ liệu UTF-8:

$ 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

(Các \xs giúp phát hiện các ký tự không hợp lệ (ngoại trừ dương tính giả tự nguyện được giới thiệu bằng chữ \xE9 ở trên)).

(nhiều cách sử dụng Nice khác).

7
Stéphane Chazelas

Python đã có hàm unicode tích hợp kể từ phiên bản 2.0.

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

Trong Python 3, unicode đã được gấp lại thành str . Nó cần được truyền một giống như byte đối tượng , ở đây các đối tượng cơ bản buffer cho mô tả chuẩn .

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

Tôi đã gặp vấn đề tương tự (chi tiết trong phần "Bối cảnh") và đi kèm với ftfy_line_by_line.py giải pháp:

#!/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"))

Sử dụng encode + thay thế + ftfy để tự động sửa Mojibake và các sửa lỗi khác.

Bối cảnh

Tôi đã thu thập> 10GiB CSV của siêu dữ liệu hệ thống tệp cơ bản bằng cách sử dụng sau gen_basic_files_metadata.csv.sh script, chạy về cơ bản:

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

Sự cố tôi gặp phải với mã hóa tên tệp không nhất quán trên các hệ thống tệp, gây ra UnicodeDecodeError khi xử lý thêm với python ứng dụng ( csvsql để cụ thể hơn).

Vì vậy, tôi đã áp dụng kịch bản ftfy ở trên, và nó đã mất

Xin lưu ý ftfy khá chậm, xử lý những người> 10GiB đã thực hiện:

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

trong khi sha256sum để so sánh:

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

trên CPU Intel (R) Core (TM) i7-3520M @ 2.90GHz + 16GiB RAM (và dữ liệu trên ổ đĩa ngoài)

1
Grzegorz Wierzowiecki