Perlのプログラムにて、あるファイルがプレーンテキストのみで書かれていることを  
正規表現で判別したいのですが、どのようにパターンを書けば実現出来るか悩んでいます。  
Linuxのfileコマンドでテキスト/バイナリの識別ができますが、  
そのようなことを正規表現で書けるものでしょうか。
CC BY-SA 3.0
2015-01-25T10:23:29.640
5246
2015-04-09T13:46:24.503
2015-01-25T10:30:56.150
7787
7787
question
正規表現 perl
プレーンテキストであることを確認するための正規表現
「プレーンテキスト」の定義が「表示可能な文字データだけを含む」であるとするならば、`\p{Print}` で充分かもしれません。`Print`
が何者かはマニュアル `perluniprops`(1) を参照のこと。

例えば入力の文字エンコーディングが UTF-8 とするなら、次のようになります。

```

    $ perl -MEncode -e '$/=""; $i=<>; eval { $i=Encode::decode("UTF-8", $i, Encode::FB_CROAK); }; print(((!$@ && $i!~/[^\p{Print}\t\n]/) ? "Text" : "Binary"), "\n")' /etc/passwd
    Text
    $ perl -MEncode -e '$/=""; $i=<>; eval { $i=Encode::decode("UTF-8", $i, Encode::FB_CROAK); }; print(((!$@ && $i!~/[^\p{Print}\t\n]/) ? "Text" : "Binary"), "\n")' /bin/ls
    Binary
    
```

評価対象の変数の UTF8 フラグを立ること (`perlunitut`(1) や `Encode` モジュールのマニュアル (`perldoc
Encode` を実行すると読めます) を参照)、改行コードとタブもテキストと識別したいなら正規表現の文字クラスに `\t\n`
を含める点が重要です。`\r` も含めることも検討してください。

入力が Shift_JIS ならば `Encode::decode` の第一引数を調整してください。

```

    $ perl -MEncode -e '$/=""; $i=<>; eval { $i=Encode::decode("Shift_JIS", $i, Encode::FB_CROAK); }; print(((!$@ && $i!~/[^\p{Print}\t\n]/) ? "Text" : "Binary"), "\n")' sjis.txt
    Text
    
```
CC BY-SA 3.0
2015-01-25T11:57:41.960
5252
2015-01-25T13:02:26.780
2015-01-25T13:02:26.780
3061
3061
5246
answer
正規表現でやらずにバイナリデータとしてShift_JISとUTF-8を同時に判定したほうが良いはと思いますが、一応正規表現の形でShift-
JISの判定法を書きます。

[Shift_JISはシングルバイトが`00-7F,A0-DF`で2バイト文字が`80-9F`と`40-7E,80-FC`というルールがある](https://ja.wikipedia.org/wiki/Shift_JIS#.E3.82.B3.E3.83.BC.E3.83.89.E7.A9.BA.E9.96.93.E3.81.AB.E3.81.8A.E3.81.91.E3.82.8B.E6.96.87.E5.AD.97.E6.95.B0.E5.88.B6.E9.99.90)のでバイトを分類すると

  * SB: `[\x00-\x3F\x7F]`
  * SB/MB2: `[\x40-\x7E\xA0-\xDF]`
  * MB1/MB2: `[\x80-\x9F]`
  * MB2: `[\xE0-\xFC]`
  * 未使用: `[\xFD-\xFF]`

となります。(SB=シングルバイト、MBn=マルチバイトのn文字目)

状態遷移はSB→MB2、MB2→MB2が禁止されているので結局`(未使用|[^MB1/MB2][MB2])`となって

```

    ([\xFD-\xFF]|[^\x80-\x9F][\xE0-\xFC])
    
```

がNGとなるパターンですかねえ。なお文字列が2バイト文字の1バイト目で切れているパターンは考慮していないです。

[UTF-8は`11******`がマルチバイトの先頭で、上位ビットの1がn連続であればn-1個`10******`というバイトが続きます(2≦n≦6)](https://ja.wikipedia.org/wiki/UTF-8)。つまりC0以上のバイトを5通りに分類して後ろに80-BFのバイトが所定の個数並んでいるかを調べればいいです。例えばn=3なら`[\xE0-\xEF]([^\x80-\xBF]|[\x80-\xBF][^\x80-\xBF]|[\x80-\xBF]{3})`がNGです。

実用性は無いので正規表現を使わずに同様のチェックを行ったほうが良いです。
CC BY-SA 3.0
2015-01-25T12:10:57.647
5253
2015-01-25T12:48:08.397
2015-01-25T12:48:08.397
5750
5750
5246
answer
直接正規表現を使う必要はありません。  
というか、デコードされていないバイト文字列に正規表現を直接ひっかけるのは労多くして功少なしです。  
そのかわり、`Encode::Guess`で代用するのはどうでしょうか？

* * *

プレーンテキストはバイナリの集まりで構成されています。  
ですので、本来プレーンテキストとバイナリの間に明確な区別はありません。  
プレーンテキストはあくまで人間の目がバイト列をそのように認識しているだけです。

Shift_JISには使用されないバイト列のパターンも存在しますから、  
それを **運良く** 拾ってこれるなら「Shift_JISではない」と言うことはできます。  
もちろんLinuxのfileコマンドも例外ではありません。

> perlfaq6 - マルチバイト文字を含む文字列をマッチングさせるには?  
>
> <http://perldoc.jp/docs/perl/5.14.1/perlfaq6.pod#How32can32I32match32strings32with32multibyte32characters63>

プレーンテキストかバイナリかを機械的に見分けるには、結局統計判断に頼ることになります。

もしプレーンテキストというのがasciiまたはutf8だけを指しているのであれば、ファイルを`open`するまでもなく`-T`ファイルテストを利用できます。

```

    $ perl -E 'say "TEXT" if -T "utf8.txt";'
    
```

> -T ファイルは ASCII または UTF-8 テキストファイル (発見的に推測します)。  
>  <http://perldoc.jp/func/-X>

標準入力に`find(1)`や`ls(1)`を使いたい場合はこんな感じでもOKです。

```

    $ ls | perl -nlE 'say $_ if -T;'
    
```

Shift_JISも含めるとなると、いよいよファイルを`open`する必要があります。

事前にプレーンテキストの条件として`Shift_JIS`でいいのか再度確認して下さい。  
歴史的な経緯もあって、一般には`Shift_JIS`と呼ばれていても実際は`Windows-31j
(cp932)`を指している場合があります。`㈱`や`Ⅲ`を文字とみなし、かつIBM拡張文字等も気にしなくて良いのであれば、`cp932`で見るのが良さそうです。

```

    $ ls | perl -MEncode::Guess -nlE 'open $fh, "<", $_; say $_ if ref guess_encoding(do { local $/; <$fh> }, qw/utf8 cp932/);'
    
```
CC BY-SA 3.0
2015-04-09T13:46:24.503
8959
2015-04-09T13:46:24.503
62
5246
answer