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


50

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 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 theo định nghĩa của UTF-8 , hãy tiếp tục.


Xem thêm keithdevens.com/weblog/archive/2004/Jun/29/UTF-8.regex để biết regex có thể.
Mikel

@Mikel: hoặc unix.stackexchange.com/questions/6460/ Ấn Độ Tôi đã hy vọng cho một cái gì đó ít vụng về hơn.
Gilles 'SO- đừng trở nên xấu xa'

Câu trả lời:


34

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ệ (ít nhất nó hoạt động với GNU Grep).


Ngoại trừ -a, đó là yêu cầu để làm việc bởi POSIX. Tuy nhiên, GNU grepít nhất không phát hiện ra UTF-8 được mã hóa UTF-16 thay thế các ký tự không phải là ký tự hoặc mã hóa trên 0x10FFFF.
Stéphane Chazelas

1
@ StéphaneChazelas Ngược lại, -aGNU cần grep(không tuân thủ POSIX, tôi giả sử). Liên quan, khu vực thay thế và các điểm mã trên 0x10FFFF, đây là một lỗi sau đó (có thể giải thích điều đó ). Đối với điều này, việc thêm -Psẽ hoạt động với GNU grep2.21 (nhưng chậm); đó là lỗi ít nhất trong Debian grep / 2.20-4 .
vinc17

Xin lỗi, thật tệ, hành vi này không được chỉ định trong POSIX vì đây greplà một tiện ích văn bản (chỉ dự kiến ​​sẽ hoạt động với kiểu nhập văn bản), vì vậy tôi cho rằng hành vi của GNU grep là hợp lệ như ở đây.
Stéphane Chazelas

@ StéphaneChazelas Tôi xác nhận rằng POSIX nói: "Các tệp đầu vào sẽ là các tệp văn bản." (mặc dù không có trong phần mô tả, đó là một chút sai lệch). Điều này cũng có nghĩa là trong trường hợp các chuỗi không hợp lệ, hành vi không được xác định bởi POSIX. Do đó, cần phải biết việc triển khai, chẳng hạn như GNU grep(có ý định coi các chuỗi không hợp lệ là không khớp) và các lỗi có thể xảy ra.
vinc17

1
Tôi đang chuyển câu trả lời được chấp nhận sang câu trả lời này (xin lỗi, Peter.O vì nó đơn giản và hoạt động tốt cho trường hợp sử dụng chính của tôi, đó là một cách giải quyết để phân biệt UTF-8 với các mã hóa thông thường khác (đặc biệt là mã hóa 8 bit). Stéphane ChazelasPeter.O cung cấp câu trả lời chính xác hơn về mặt tuân thủ UTF-8
Gilles 'SO- ngừng trở nên xấu xa'

33

Tôi nghĩ rằng 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 sang thiết bị lỗi chuẩn, 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.


Ok, đó là iconv -c -t UTF-8 <input.txt | diff input.txt - | sed -ne 's/^< //p'. Tuy nhiên, nó sẽ không hoạt động như một đường ống dẫn, vì bạn cần đọc đầu vào hai lần (không, teesẽ không làm, nó có thể chặn tùy thuộc vào mức độ đệm iconvdifflàm).
Gilles 'SO- đừng trở nên xấu xa'

2
Lưu ý ngẫu nhiên: đầu vào và đầu ra có thể không phải là cùng một tệp hoặc bạn sẽ kết thúc với một tệp trống
drahnr

1
Hoặc sử dụng thay thế quy trình nếu vỏ của bạn hỗ trợ nódiff <(iconv -c -t UTF-8 <input.txt) input.txt
Karl

Làm thế nào để bạn làm điều này và làm cho đầu ra vào cùng một tệp với đầu vào. Tôi vừa làm điều này và nhận được một tập tin trống iconv -c -t UTF-8 <input.txt> input.txt
Costas Vrahimis

1
Cảm ơn .. Điều này cho phép khôi phục bãi rác postgresql bị hỏng, nhưng không loại bỏ utf-8 hợp lệ
Exclusiviji

21

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 mẫu UTF-8 không hợp lệ, để tuân thủ nghiêm ngặt 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 biểu thức chính quy 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 kiểm tra (sử dụng iconvlà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 trang UTF-8 wikipedia này ,.

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

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

Đây khối nguyên là mất tích từ phổ Unicode codepoints, vì nhu cầu mã hóa UTF-16 để bước xa hơn ý định 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 0x0800số nguyên đã được dành riêng để sử dụng bởi UTF-16 .. Khối này mở rộng phạm vi 0x00D800đế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 Thử nghiệm 1 , số regexnày đã được kiểm tra đối với mọi số trong phạm vi của Bộ mã Unicode và nó phù hợp với kết quả của iconv .. tức là. Các giá trị hợp lệ 0x010F7FF0x000800 giá 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ị số nguyên tối đa là 0x7FFFFFFF ?
Để tạo các giá trị byte UTF-8 không cần thiết , 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 cái kia .. Tôi không thể sử dụng iconvvì nó chỉ xử lý tập hợp con Tiêu chuẩn Unicode hợp lệ của UTF-8 rộng hơn (bản gốc) Tiêu chuẩn...

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 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

Vấn đề chính với regrec của tôi là nó cho phép một số chuỗi bị cấm như \300\200(thực sự xấu: đó là điểm mã 0 không được biểu thị bằng một byte null!). Tôi nghĩ rằng regex của bạn từ chối chúng một cách chính xác.
Gilles 'SO- ngừng trở nên xấu xa'

7

Tôi thấy uconv(trong icu-devtoolsgó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 \xtrợ 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 với một nghĩa đen \xE9ở trên)).

(nhiều công dụng tốt đẹp khác).


Tôi nghĩ recodecó thể được sử dụng tương tự - ngoại trừ việc tôi nghĩ nó sẽ thất bại nếu được yêu cầu dịch một chuỗi đa bào không hợp lệ. Tôi không chắc chắn mặc dù; nó sẽ không thất bại cho print...|recode u8..u8/x4ví dụ (mà chỉ làm một hexdump như bạn làm ở trên) vì nó không làm bất cứ điều gì nhưng iconv data data, nhưng nó không thất bại như recode u8..u2..u8/x4vì nó dịch rồi in. Nhưng tôi không biết đủ về nó để chắc chắn - và có rất nhiều khả năng.
mikeerv

Nếu tôi có một tập tin, nói , test.txt. Làm thế nào tôi nên giả sử tìm thấy ký tự không hợp lệ bằng cách sử dụng giải pháp của bạn? Những gì hiện ustrong mã của bạn nghĩa là gì?
jdhao

@Hao, uscó nghĩa là Hoa Kỳ, viết tắt của ASCII. Nó chuyển đổi đầu vào thành một ASCII trong đó các ký tự không phải ASCII được chuyển đổi thành \uXXXXký hiệu và không ký tự thành \xXX.
Stéphane Chazelas

Tôi nên đặt tập tin của mình ở đâu để sử dụng tập lệnh của bạn? Là dòng cuối cùng trong mã chặn đầu ra của mã của bạn? Đó là một chút khó hiểu với tôi.
jdhao

4

Python đã có một built-in unicodechức năng 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 phải được thông qua một đối tượng giống như byte , ở đây các bufferđối tượng cơ bản cho các 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)

Người python 2ta không gắn cờ UTF-8 được mã hóa UTF-16 không thay thế ký tự (ít nhất là với 2.7.6).
Stéphane Chazelas

@ StéphaneChazelas Chết tiệt. Cảm ơn. Cho đến nay tôi chỉ chạy thử nghiệm danh nghĩa, tôi sẽ chạy thử pin của Peter sau đó.
Gilles 'SO- ngừng trở nên xấu xa'

1

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 giải pháp ftfy_line_by_line.py :

#!/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 chỉnh sửa 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 tập lệnh gen_basic_files_metadata.csv.sh , chạy về cơ bản:

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

Các rắc rối tôi đã có được với mã hóa không phù hợp của tên tập tin trên hệ thống tập tin, gây ra UnicodeDecodeErrorkhi xử lý hơn nữa với các ứng dụng python ( csvsql cho 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)


Và vâng, tôi biết rằng lệnh find này sẽ không mã hóa đúng tên tệp chứa dấu ngoặc kép theo tiêu chuẩn csv
Grzegorz Wierzowiecki
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.