잘못된 utf8 필터링


50

알 수 없거나 혼합 된 인코딩의 텍스트 파일이 있습니다. UTF-8이 아닌 바이트 시퀀스를 포함하는 줄을보고 싶습니다 (텍스트 파일을 일부 프로그램으로 파이핑하여). 마찬가지로, 유효한 UTF-8 행을 필터링하고 싶습니다. 즉, 찾고 있습니다 .grep [notutf8]

이상적인 솔루션은 이식 가능하고 짧고 다른 인코딩에 일반화 할 수 있지만 UTF-8 정의 에서 굽는 것이 가장 좋은 방법이라고 생각 되면 계속하십시오.


가능한 정규식에 대해서는 keithdevens.com/weblog/archive/2004/Jun/29/UTF-8.regex 도 참조하십시오 .
Mikel

@Mikel : 또는 unix.stackexchange.com/questions/6460/…… 나는 덜 서투른 것을 기대하고있었습니다.
Gilles 'SO- 악마 그만해'

답변:


34

을 사용하려면 grep다음을 수행하십시오.

grep -axv '.*' file

UTF-8 로켈에서 적어도 유효하지 않은 UTF-8 시퀀스를 가진 행을 가져옵니다 (적어도 GNU Grep에서 작동 함).


를 제외하고 -aPOSIX에서 작동해야합니다. 그러나 GNU grep는 UTF-8로 인코딩 된 UTF-16에서 0x10FFFF보다 큰 문자 나 코드 포인트를 찾아 내지 못합니다.
Stéphane Chazelas

1
@ StéphaneChazelas 반대로, 이것은 -aGNU에 필요합니다 grep(POSIX 호환이 아니라고 가정합니다). 대리 영역과 0x10FFFF 이상의 코드 포인트에 관해서는 버그입니다 (이를 설명 할 수 있음 ). 이를 위해 추가 -P는 GNU grep2.21에서 작동해야 하지만 느립니다. 적어도 데비안 grep / 2.20-4 에서는 버그가 있습니다.
vinc17

죄송합니다. POSIX grep에서는 텍스트 유틸리티이므로 텍스트 입력에서만 작동 하므로 동작이 지정되어 있지 않으므로 GNU grep의 동작이 여기에서와 같이 유효하다고 생각합니다.
Stéphane Chazelas

@ StéphaneChazelas POSIX가 "입력 파일은 텍스트 파일이어야합니다"라고 말합니다. (설명 부분에는 없지만 약간 오해의 소지가 있습니다). 이는 유효하지 않은 시퀀스의 경우 POSIX에 의해 동작이 정의되지 않음을 의미합니다. 따라서 GNU grep(유효하지 않은 시퀀스를 비 일치로 간주하려는 의도) 및 가능한 버그와 같은 구현을 알아야합니다 .
vinc17

1
미안, (이 하나를 허용 대답을 전환하고있어 Peter.O가 이 간단하고 다른 일반적인 인코딩 (특히 8 비트 인코딩)에서 UTF-8을 구별하는 휴리스틱 내 주요 사용 사례, 잘 작동하기 때문이다. 스테판을 ChazelasPeter.O 는 UTF-8 준수 측면에서보다 정확한 답변을 제공합니다
Gilles 'SO-stop

33

아마 당신이 iconv 원한다고 생각합니다 . 코드 세트 간 변환을위한 것이며 불규칙한 형식을 지원합니다. 예를 들어 UTF-8에서 유효하지 않은 것을 제거하려면 다음을 사용할 수 있습니다.

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

-c 옵션을 사용하지 않으면 stderr로 변환 할 때 문제점이보고되므로 프로세스 방향으로 이러한 목록을 저장할 수 있습니다. 다른 방법은 비 UTF8 항목을 제거한 다음

diff input.txt output.txt

변경 사항이있는 목록.


알았어 iconv -c -t UTF-8 <input.txt | diff input.txt - | sed -ne 's/^< //p'. 그러나 입력을 두 번 읽어야하므로 파이프 라인으로 작동하지 않습니다 (아니요, tee버퍼링 iconvdiff수행 량에 따라 차단 될 수 있음).
Gilles 'SO- 악마 그만해'

2
랜덤 노트 : 입력과 출력은 같은 파일이 아니거나 빈 파일로 끝납니다
drahnr

1
또는 쉘이 지원하는 경우 프로세스 대체를 사용하십시오diff <(iconv -c -t UTF-8 <input.txt) input.txt
Karl

이 작업을 어떻게 수행하고 입력과 동일한 파일로 출력합니다. 난 그냥 이런 짓을하고 빈 파일을 가지고의 iconv -c -t UTF-8 <input.txt를> input.txt를
타스 Vrahimis

1
덕분에 ..이 깨진 UTF-8의 PostgreSQL 덤프를 복원 할 수 있지만, 폐기 유효하지 UTF-8
Superbiji

21

편집 : 정규식에서 오타 버그를 수정했습니다 . \ 80이 아닌 '\ x80`이 필요했습니다 .

UTF-8을 엄격하게 준수하기 위해 잘못된 UTF-8 양식을 필터링하는 정규식 은 다음과 같습니다.

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'

테스트 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)          

Q. 유효하지 않은 유니 코드를 필터링하는 정규식을 테스트하기 위해 테스트 데이터를 어떻게 작성합니까?
A. 나만의 UTF-8 테스트 알고리즘을 만들고 규칙을 어기십시오.
Catch-22. 그러나 어떻게 테스트 알고리즘을 테스트합니까?

정규 표현식은, 상기 (하여 테스트 한 iconv행의 모든 정수 값을 기준으로) 0x000000x10FFFF받아이 상한값 .. 유니 코드 코드 포인트의 최대 정수 값

위키 백과 UTF-8 페이지 에 따르면 .

  • UTF-8은 1-4 개의 8 비트 바이트를 사용하여 유니 코드 문자 집합의 1,112,064 코드 포인트 각각을 인코딩합니다.

이 숫자 (1,112,064)는 최대 유니 코드 코드 포인트에 대한 실제 최대 정수 값보다 0x0800 만큼 적은 범위 0x000000에 해당합니다 0x10F7FF.0x10FFFF

정수 블록은 UTF-16 인코딩이 surrogate pairs 라는 시스템을 통해 원래 디자인 의도넘어서야 하기 때문에 Unicode Codepoints 스펙트럼에서 누락되었습니다 . 정수 블록은 UTF-16에서 사용하도록 예약되었습니다.이 블록의 범위는 ~ 입니다. 이러한 정수 중 어느 것도 유효한 유니 코드 값이 아니므로 유효하지 않은 UTF-8 값입니다. 0x08000x00D8000x00DFFF

테스트 1 에서는 regex유니 코드 코드 포인트 범위의 모든 숫자에 대해 테스트되었으며 , 다음 의 결과와 정확히 일치합니다 iconv . 0x010F7FF 유효 값 및 0x000800 유효하지 않은 값

그러나이 문제는 이제 * 정규 표현식이 범위 밖의 UTF-8 값을 어떻게 처리합니까? 위의 0x010FFFF(UTF-8은 최대 정수 값이 0x7FFFFFFF 인 6 바이트로 확장 할 수 있습니까?
필요한 * 비 유니 코드 UTF-8 바이트 값 을 생성하려면 다음 명령을 사용했습니다.

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

그들의 유효성을 (어떤 방식으로) 테스트하기 위해 Gilles'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'

'perl 's print chr'의 출력은 Gilles의 정규식 필터링과 일치합니다. 하나는 다른 것의 유효성을 강화합니다. iconv더 넓은 (원본) UTF-8의 유효한 유니 코드 표준 하위 집합 만 처리하므로 사용할 수 없습니다. 표준...

관련된 수녀는 다소 크므로, 범위의 상한, 범위 하한 및 11111, 13579, 33333, 53441과 같은 증분으로 단계별 스캔을 테스트했습니다. 결과가 모두 일치하므로 지금은 일치합니다. 남은 것은이 범위를 벗어난 UTF-8 스타일 값에 대해 정규식을 테스트하는 것입니다 (유니 코드에는 유효하지 않으므로 엄격한 UTF-8 자체에는 유효하지 않음) ..


테스트 모듈은 다음과 같습니다.

[[ "$(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

내 정규 표현식의 주요 문제는 다음과 같은 금지 된 시퀀스를 허용한다는 것입니다 \300\200(실제로 나쁜 점 : 코드 바이트 0은 null 바이트로 표현되지 않습니다!). 정규 표현식이 올바르게 거부한다고 생각합니다.
Gilles 'SO- 악마 중지'12

7

내가 발견 uconv(에 icu-devtoolsUTF-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

( \xs는 유효하지 않은 문자를 찾는 데 도움이됩니다 ( \xE9위 의 리터럴로 자발적으로 도입 된 오 탐지 제외 ).

(많은 다른 좋은 사용법).


나는 유효하지 않은 멀티 바이트 시퀀스를 번역하도록 요청하면 실패해야한다는 recode것을 제외하고는 비슷하게 사용할 수 있다고 생각 합니다. 그래도 확실하지 않습니다. 그것은 실패하지 않습니다 print...|recode u8..u8/x4예를 들어 (당신이 위의 수행과 같은 16 진 덤프를 수행하는) 는 아무것도하지 않고 있기 때문에 iconv data data,하지만 실패하는 것처럼 recode u8..u2..u8/x4그 다음 인쇄를 변환하기 때문이다. 그러나 나는 그것에 대해 확신 할만 큼 충분하지 않으며 많은 가능성이 있습니다.
mikeserv

파일이 있으면이라고 말합니다 test.txt. 솔루션을 사용하여 유효하지 않은 문자를 찾으려면 어떻게해야합니까? us코드에서 무엇을 의미합니까?
jdhao

@Hao us는 미국을 의미하며 ASCII의 약자입니다. 입력은 ASCII가 아닌 문자가 \uXXXX표기법 으로 변환되고 문자가 아닌 문자가로 변환되는 ASCII 문자로 변환 됩니다 \xXX.
Stéphane Chazelas

스크립트를 사용하려면 파일을 어디에 두어야합니까? 코드의 마지막 줄이 코드의 출력입니까? 조금 혼란 스럽습니다.
jdhao

4

파이썬은 버전 2.0부터 내장 unicode함수를 가지고 있습니다.

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

파이썬 3에서는 unicode로 접혔습니다 str. 바이트와 ​​같은 객체 , 여기 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 2한 (적어도 2.7.6로) 플래그 UTF-8 인코딩 UTF-16 대리 비 문자 실패.
Stéphane Chazelas

@ StéphaneChazelas Dammit. 감사. 지금까지는 명목 테스트 만 수행했으며 나중에 Peter의 테스트 배터리를 실행합니다.
Gilles 'SO- 악마 그만해

1

비슷한 문제 ( "컨텍스트"섹션에 자세히 나와 있음)가 나타 으며 다음 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"))

encode + replace + ftfy 를 사용하여 Mojibake 및 기타 수정을 자동 수정합니다.

문맥

기본적으로 실행되는 다음 gen_basic_files_metadata.csv.sh 스크립트를 사용하여> 10GiB CSV의 기본 파일 시스템 메타 데이터를 수집했습니다 .

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

문제가 내가 가진이 함께했다 파일 이름의 일관성 인코딩 원인이 파일 시스템에서 UnicodeDecodeError(파이썬 애플리케이션을 더 처리 할 때 csvsql이 더 구체적으로).

따라서 위의 ftfy 스크립트를 적용했으며

유의하시기 바랍니다 ftfy 꽤 느린, 10GiB 걸린 사람들> 처리 :

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

비교를 위해 sha256sum 동안 :

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

Intel (R) Core (TM) i7-3520M CPU @ 2.90GHz + 16GiB RAM (및 외장 드라이브의 데이터)


그리고 그렇습니다.이 find 명령은 csv 표준에 따라 따옴표를 포함하는 파일 이름을 올바르게 인코딩하지 않습니다.
Grzegorz Wierzowiecki
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.