テキストの unpack (ruby, php & perl)


リライト対象に ruby も混ざる事がある。それで文字列分割に便利な、テキストの `unpack` について ruby, php の場合と perl と併せメモっておく。

説明はテキスト相手用となっているので、バイナリを相手にしたい場合はドキュメントにあたること[^1]。

[^1]: また、ここで使ってる単語は適当なので各言語の該当単語に脳内変換が必要。


## SYNOPSYS

```
$ perl -le 'print unpack "A4", "Hello"'
Hell
$ ruby -e 'puts "Hello".unpack("A4")'
Hell
$ php -r 'print_r( unpack("A4h", "Hello") );'
Array
(
     [h] => Hell
)
```



## 説明

```
# perl
ARRAY = unpack TEMPLATE, STRING ;
# ruby
ARRAY = STRING.unpack(TEMPLATE)
# php
HASH  = unpack(TEMPLATE, STRING, [OFFSET]) ;
```

どの言語でも、STRING を受け取って TEMPLATE に従って分割する。
戻り値として、perl, ruby は配列(リスト)、php は連想配列を返す。

php では、OFFSET を第3引数に指定する事が出来るが、同様の挙動を再現出来る、後述の `x` とか `@` とかがある、、、


|言語|対象|戻り値|繰り返し|キー|OFFSET|
|:-:| :-: |:-:|:-:|:-:|:-:|
|perl| STRING |配列|○|×|×|
|ruby| STRING |配列|×|×|×|
|php| STRING |連想配列|×|○|○|

- perl は`()`によるグループ化と繰り返しを指定する事が可能。
- phpは戻り値が連想配列。そのためにキーを設定することが可能[^2]。

[^2]: と言うか設定しないと使いものにならない。


----

### TEMPLATEについて


perlとrubyは、

> 値の型等を指定する文字列 + 文字数を指定する数字

で構成する。

上の例だと型に A(ascii) を指定して 4 が文字長を指定している。

php は、それに加え、出力連想配列のキーとなる任意の文字列が必要となる。上の例で言えば、"A4h" の "h"。


ここで、 Hell と o を得たい場合。

perl と ruby では "A4A1" と羅列すればいい。 

```
$ perl -le 'print for unpack "A4A1", "Hello"'
Hell
o
$ ruby -e 'puts "Hello".unpack("A4A1")'
Hell
o
```

php の場合は、`/` を区切り文字として "A4h/A1o"に連結。もちろんキーは同じにしてはいけない。

```
$ php -r 'print_r( unpack("A4h/A1o", "Hello") );'
Array
(
    [h] => Hell
    [o] => o
)
```

もっと分割したい場合は、さらに `/` で区切ってフォーマットを記載していく。

### 文字型について

A 以外にも、ascii 相手で覚えておけば得をする文字型がある。

| 型 | 内容 | 備考 |
|:-:|:-:| :-:|
|A | テキスト文字列 | -|
|x| 指定文字数飛ばす | x4 なら 4文字飛ばす |
|X| 指定文字数戻る | X4 なら 4文字戻る |
|@| 文字列の絶対位置へ移動 | @1 なら 2文字目 |

\# perl の @ は実際には別の定義となっている。

(手軽なので YYYYmmdd な文字列の分割を例にしてしまうが、)これを利用すると m, d だけを取得したり[^3]、

[^3]: この例では、x を @ に置き換えても同じ結果が得られる。但し、perl のワンライナーではコーテーションを q{} にしないとコケることがあるが。 

```
$ perl -le 'print for unpack "x4A2A2", "20210309"'
03
09
$ ruby -e 'puts "20210309".unpack("x4A2A2")'
03
09
$ php -r 'print_r( unpack( "x4/A2m/A2d", "20210309") ); '
Array
(
    [m] => 03
    [d] => 09
)
```

ym, y, m, d などを一気に分割したり、

```
$ perl -le 'print for unpack "A6X6A4A2A2", "20210309"'
202103
2021
03
09
$ ruby -e 'puts "20210309".unpack("A6X6A4A2A2")'
202103
2021
03
09
$ php -r 'print_r( unpack( "A6ym/X6/A4y/A2m/A2d", "20210309") ); '
Array
(
    [ym] => 202103
    [y] => 2021
    [m] => 03
    [d] => 09
)
```

することが可能となる[^4]。

[^4]: お気付きだと思うが、php で非出力対象のテンプレートにはキーを付ける必要はない。

**@について**

perl, ruby と php で若干の違いがみられるのでメモをしておく。

手元の php ver. 7.2.24 では、`@0` が使えない。

```
# perl は略
$ ruby -e 'puts "hoge".unpack("A1@0A1")'
h
h
$ ruby -e 'puts "hoge".unpack("A1@2A1")'
h
g
```

```
$ php -r 'print_r( unpack( "A1x/@0/A1y", "hoge") ); '
Array
(
    [x] => h
    [y] => o
)
$ php -r 'print_r( unpack( "A1x/@2/A1y", "hoge") ); '
Array
(
    [x] => h
    [y] => g
)
```

php では、指定自体がされていない様に振る舞う。



## ruby だけの特記事項

`unpack1`なるものがあるそうで、最初の要素をstringとして戻すそうだ、、、

<br />
<br />
***perl に興味がない人は、ここで終了***

----

<br />
<br />


## perl だけの特記事項

### ()

perl の場合は、`()`でグループ化が可能で、回数指定なども可能[^5][^6]

[^5]: 最後の例は、`perldoc packtut` より。
[^6]: ruby も実は、`()`を含めて記載する事は出来るが、存在自体を無視するっぽい。

```
$ perl  -le 'print for unpack "(A2)2", "hoge"'
ho
ge
$ perl -le 'print for unpack "(A2A1)2", "hogehoge"'
ho
g
eh
o
$ perl -le 'print for unpack "(xAX2Ax)*", "hogehoge"'
o
h
e
g
o
h
e
g
```

実用性の有無は別として、`reverse` もこんな感じで再現出来る

```
$ perl -le 'print pack "(A)*", unpack q{@*(XAX)*}, "hogehoge" '
egohegoh
```

### length-item / string-item

perl での `/` は、php  とは意味合いが異る。

例えば、

```
2AB3CDE4FGHI1J
```
の様な、それぞれの数字が直後の文字列の長さを示す集合があった場合。

以下の様な書式で、文字列だけを切り出す事が出来る。

```
$ perl -le 'print for unpack "(A1/A)*", "2AB3CDE4FGHI1J"'
AB
CDE
FGHI
J
```

/ の前の A1 は、"長さ情報を持つ文字列はテキストで長さは1"、と言う意味で、
/ の後の A は、"上記で得られた長さ分のテキストを切り出せ"、と言う意味。

スペースすら節約したかった時代の学術データとかに、時折これ↑を利用すると解析しやすい書式が見られる[^7]。バイナリ使え、と思う事もあるが、、、

[^7]: 先人が上のギミックを知っていて学術データのフォーマットを作成したは良いが、今となってはその知識を持った人はリタイヤしてしまったってオチだろうか。

`perldoc pack` の↓も理解できるハズだ。

```
$ perl -le 'print for unpack("A3/AA*", "007 Bond  J ")'
 Bond
J
```

`pack`も可能なので、メモしておく

```
$ perl -le 'print pack "A2/A*", "GCTTTTTTTTTCGACCAAAGGTAACGAGGTAAC";'
33GCTTTTTTTTTCGACCAAAGGTAACGAGGTAAC
```

### @

@ の本来の定義は、

> @  一番内側の () の組の開始位置から数えて、絶対位置までヌル文字で
       埋めるか切り詰める。

だそうだ。
テキスト相手の `unpack` だと、`()` の組の開始位置からインデクスの位置まで移動する。

```
$ perl -le 'print for  unpack q{(@1A@0A2)*}, "hogehoge" '
o
ho
e
ge
o
ho
e
ge
```

直左 `(` の位置を覚えてくれる。

[以前](https://qiita.com/bunzaemon/items/7a3ca9c83ff3e61492af)の 5-mer への分割を `unpack` で、 `@` を使って書くと、

```
$ perl -le 'length == 5 and print for unpack q{(A5@1)*}, "GCTTTTTTTTTCGACCAAAGGTAACGAGGTAAC" '
GCTTT
CTTTT
TTTTT
TTTTT
TTTTT
TTTTT
TTTTT
TTTTC
TTTCG
TTCGA
TCGAC
CGACC
(略)
```

こんな感じで再現出来る。

### .

型の `.`

`perldoc pack` には以下の様に紹介されてるけど、

> .  値で指定した絶対位置までヌル文字で埋めるか切り詰める。


テキストの `unpack` の場合は、"インデクスを返す"だけ[^8]。

[^8]: `.`の後に数値を続けても無視される。


```
$ perl -le 'print for unpack "(A5)*.", "GCTTTTTTTTTCGACCAAAGGTAACGAGGTAAC"'
GCTTT
TTTTT
TCGAC
CAAAG
GTAAC
GAGGT
AAC
33
```

`@`と同じく`()`内ではインデクスがリセットされる。

```
$ perl -le 'print for unpack "(A5.)*.", "GCTTTTTTTTTCGACCAAAGGTAACGAGGTAAC"'
GCTTT
5
TTTTT
5
TCGAC
5
CAAAG
5
GTAAC
5
GAGGT
5
AAC
3
33
```

`length` もこんな感じで再現出来る。

```
$ perl -le 'print unpack "@*.", "GCTTTTTTTTTCGACCAAAGGTAACGAGGTAAC"'
33
```



<br />
<br />