잘못된 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을 구별하는 휴리스틱 내 주요 사용 사례, 잘 작동하기 때문이다. 스테판을 Chazelas 와 Peter.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버퍼링 iconv및 diff수행 량에 따라 차단 될 수 있음).
— 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행의 모든 정수 값을 기준으로) 0x00000에 0x10FFFF받아이 상한값 .. 유니 코드 코드 포인트의 최대 정수 값

이 위키 백과 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.