🚚

pack, unpack メソッドをなんとなく理解したい!【Ruby】

2025/01/30に公開

ラブグラフでエンジニアをしています横江( @yokoe24 )です!

Ruby を扱っていて、たまに出てくる pack メソッドや unpack メソッド。
以前書いた、emoji を1文字と数えるのが大変な記事でも扱いました。

https://zenn.dev/lovegraph/articles/a43529cc3653f6

エンコード済みの文字列を直すときに重宝するのですが、毎回使い方がよくわかりません。

よくある使い方を見ながら、なんとな〜く理解できるところまで進みたいと思います!

1. 理解を深めたい

1-1. ドキュメントを見てみる

まずは公式ドキュメント!

pack メソッドは Array に対しておこない String が返ってきて
unpack メソッドは String に対しておこない Array が返ってくる ものだとわかります。

引数は template だと書かれています。

配列の内容を template で指定された文字列にしたがって、バイナリとしてパックした文字列を返します。

pack メソッドのドキュメントにはこのように書かれているのですが、
引数 template に何を指定するべきなのか、いまいちよくわかりません……。

1-2. ソースコードを見てみる

引数 template の謎を解明すべく、ソースコードの方を確認してみます。
コードを追ってみると、どうやら以下がメインの処理っぽいとわかります。

https://github.com/ruby/ruby/blob/v3_3_7/pack.c#L936-L1611

長いですが、特に以下の処理を見てほしくて、
ここを見ると、『template で指定された文字列』の意味がなんとなくわかります。

https://github.com/ruby/ruby/blob/v3_3_7/pack.c#L1045-L1265

「a」とか「Z」とか「H」とかの文字に応じて switch 文の処理が変わっているだけですね!

引数の template というのが 「ただのフラグ」 と考えてしまえば、
先ほどの『配列の内容を template で指定された文字列にしたがって、バイナリとしてパックした文字列を返します。』という pack メソッドの説明も、なんとなくは理解できる気がしてきます。

『バイナリとしてパック』というのがまた難しいですが、
「バイナリーデータとして変換」の意味だと捉えておそらく大丈夫そうです。

pack/unpack メソッドの元ネタは Perl というプログラミング言語にありますので、
pack 関数 - futomi's CGI Café」や「pack と unpack のチュートリアル - perldoc.jp」など、
Perl について説明しているサイトが丁寧に説明してくださっていることが多いように感じました。

2. 実際によく使うもの

自分は unpack("U*") をよく使います。

冒頭に書いた『全角文字を半角文字の2文字分として数えるの、意外と難しい問題【Rails】』の記事内でも使いましたが、
U は、文字を UTF-8 で表現し直すとどんな形になるのか確認する場合に使います。

> "あ".unpack("U*")
=> [12354]

> "あいう".unpack("U*")
=> [12354, 12356, 12358]

ただし注意点としては、 10進数におけるコードポイント が出力される点です。

以下のように16進数に直すことで使いやすくなるシーンが多いと思います。

> "あ".unpack("U*").map { |code_point_dec| "U+" + code_point_dec.to_s(16) }
=> ["U+3042"]

> "あいう".unpack("U*").map { |code_point_dec| "U+" + code_point_dec.to_s(16) }
=> ["U+3042", "U+3044", "U+3046"]

Wikipedia の Unicode一覧 3000-3FFF にある記載と一致していることが確認できますね。

なお、 "U*"* を指定しなかった場合、いくつ分の変換までおこなうかの指定ができます。
以下に例を示します。

> "あいう".unpack("U")
=> [12354]
> "あいう".unpack("UU")
=> [12354, 12356]
> "あいう".unpack("UUU")
=> [12354, 12356, 12358]

> "あいう".unpack("U1")
=> [12354]
> "あいう".unpack("U2")
=> [12354, 12356]
> "あいう".unpack("U3")
=> [12354, 12356, 12358]

> "あいう".unpack("U5")
=> [12354, 12356, 12358]

3. おしまい

このように、 pack メソッドや unpack メソッドが
規則に従って文字列などを変換してくれるものだということが、なんとなく伝わったでしょうか。
規則ごとに結果が異なるので、「pack/unpackメソッドはこう使う!」と明確に言えないぶん、理解するのが難しいように感じます。

複数のデータを一つの固定長データ文字列に直すことでファイルサイズを抑える pack メソッドと、
その pack メソッドで固めたものを再びデータとして見えるようにするものが unpack メソッドであるといった説明が多いので、
今回例示したエンコード済み文字列とコード番号との変換などは変わった使い方をしているとも言えそうで、だから理解がなおのこと難しいのかもしれません。

あまり実務で使う機会が多いものではないですが、
文字が UTF-8 ではどのように表現されているかの確認をしたいときには特に有用ですので、
存在だけでも覚えておくと、突然役立つときが来ますね!

ラブグラフのエンジニアブログ

Discussion