Shell Script でバイナリファイルを 2 進数に変換する

公開日:
最終更新日:
バイナリファイルを 2 進数に変換する

バイナリファイルを読みたいと思っても、うまく開けなかったり、16 進数で表示されたりして、うまく 2 進数に変換されなくて少し手間だったりしませんか? (無知なだけなのか…?)
今回はバイナリファイルを 4 バイト区切りの二進数に変換するシェルスクリプトを書いていきます。
これを少し応用すると、一瞬で 2 進数の思った通りのフォーマットに変換するシェルスクリプトが書けるはずです!

とりあえず、コードだけ見たいという人は「3.2. シェルスクリプトを書く」、「3.3. さらに変形したい」を見てください。

1. 前提

とりあえずバイナリファイルを読むなら、VSCode に拡張機能を入れたり、Vim で加工したり、

以下のようなコマンドを利用すればできますが、思い通りに 2 進数に変換するのは難しいような気がします。

  • hexdump (8 進数や 16 進数でダンプするコマンド。)
  • xxd (16 進数、または 2 進数でダンプするコマンド。16 進ダンプから元のデータに復元も可能。)
  • od (8 進数や 16 進数などでダンプするコマンド。)

この中で 2 進数に変換してくれるのは xxdコマンドだけですが、このコマンドではフォーマットが思い通りに指定できず、4 バイト区切りの二進数 表示に直すことはできませんでした。(できたら教えてください…)

2. bc コマンド

他にうまく二進数表示ができるコマンドがないか調べたところ、bc というコマンドが見つかりました。
このコマンドは対話的に計算を行うという用途で用いられることが多いですが、入力の基数を ibase (デフォルトは 10) で、出力の基数を obase (デフォルトは 10) で指定することで、基数の変換ができます。

例えば、以下のように実行することで 16 進数を 2 進数に変換することができます。

$ echo "obase=2; ibase=16; D68B" | bc
1101011010001011


その他の詳しい使い方は以下の記事などが参考になると思います。

このコマンドをうまく活用することで、バイナリファイルを 2 進数で表示できるのではないかと考えました。

3. 変換の流れ

何となくイメージが湧いてきたので一気に結論まで行ってしまいます。

具体的には以下の流れで変換を行います。

  • hexdump でバイナリファイルを 16 進数に変換する
  • bc で再度 2 進数に戻す
  • sed を使って整形する

一度 16 進数に直して、もう一度 2 進数に戻すのは少し無駄な気がしますが、今のところこれが一番いいのではないかと思っています。

では、今回はこのフィボナッチ数列の 10 番目の値を求める RISC-V 用のバイナリファイル fib.bin を用いて実験を行いたいと思います。

3.1. fib.bin のダウンロード

この URL (https://github.com/hashi0203/riscv-processor/blob/main/test-programs/fib.bin) にアクセスして、DownloadView raw を押すと fib.bin がダウンロードできます。

3.2. シェルスクリプトを書く

次にシェルスクリプトを書いていきますが、結論から言うと以下のようになります。

#!/bin/sh
hexdump -v -e '/4 "%08X" "\n"' ${1} | while read line; do
	echo "obase=2; ibase=16; ${line}" | bc | printf "%32s\n" $(cat) | sed -e 's/ /0/g'
done

このファイルに実行権限を与えて、以下のようにすれば実行できます。
${1} は第一引数なので、ここでは /path/to/fib.binになっています。

$ cd /path/to/binfile2binary.sh # binfile2binary.sh の入っているディレクトリに移動
$ chmod +x binfile2binary.sh # binfile2binary.sh に実行権限を付与
$ ./binfile2binary.sh /path/to/fib.bin # binfile2binary.sh を実行
00000111010000000000000011101111
11111110000000010000000100010011
00000000000100010010111000100011
00000000100000010010110000100011
00000000100100010010101000100011
00000010000000010000010000010011
11111110101001000010011000100011
11111110110001000010011100000011
00000000000100000000011110010011
00000000111001111100011001100011
00000000000100000000011110010011
00000011000000000000000001101111
11111110110001000010011110000011
11111111111101111000011110010011
00000000000001111000010100010011
11111100100111111111000011101111
00000000000001010000010010010011
11111110110001000010011110000011
11111111111001111000011110010011
00000000000001111000010100010011
11111011010111111111000011101111
00000000000001010000011110010011
00000000111101001000011110110011
00000000000001111000010100010011
00000001110000010010000010000011
00000001100000010010010000000011
00000001010000010010010010000011
00000010000000010000000100010011
00000000000000001000000001100111
11111111000000010000000100010011
00000000000100010010011000100011
00000000100000010010010000100011
00000001000000010000010000010011
00000000101000000000010100010011
11110111110111111111000011101111
00000000000000000000000001101111

下の 0 と 1 が並んだ出力が欲しかった出力になっています 👏

次に具体的にシェルスクリプトでは何をしているか説明します。

まず、hexdumpコマンドの -e オプションを使って、バイナリファイルを 4 バイトずつ (/4) 0 埋めされた 8 桁の 16 進数表示 (%08X) に変換して、改行 (\n) をつけて出力しています。
ちなみに-v がないと同じ行が 2 行続いたときに * で省略されてしまいます。
実際にhexdump の部分だけを実行してみると以下のようになります。

$ hexdump -v -e '/4 "%08X" "\n"' fib.bin
074000EF
FE010113
00112E23
00812C23
00912A23
02010413
FEA42623
FEC42703
00100793
00E7C663
00100793
0300006F
FEC42783
FFF78793
00078513
FC9FF0EF
00050493
FEC42783
FFE78793
00078513
FB5FF0EF
00050793
00F487B3
00078513
01C12083
01812403
01412483
02010113
00008067
FF010113
00112623
00812423
01010413
00A00513
F7DFF0EF
0000006F

この hexdump の使い方について詳しくは以下の記事などを参考にしてください。

さらに、この出力をパイプで while read line; に渡すことで、1行ずつ lineという変数に入れていくことができるので、whileの中では 1 行ずつ処理を行なっている。

次に各行の処理、つまり whileの中での処理について考えると、まず echo "obase=2; ibase=16; ${line}" | bc で 2 進数に変換し、それを printf "%32s\n" $(cat) で 32 文字からなる文字列として表示し、sed -e 's/ /0/g'で先頭の半角スペースを 0 に置換しています。

以下の記事によるとprintfにパイプで値を渡すときには $(cat)を使うといいそうです。

また、while readsedの使い方は以下の記事をそれぞれ参考にしてください。

3.3. さらに変形したい

これで当初の目標であった バイナリファイルを 4 バイト区切りの二進数に変換する は達成されました 👏
しかし、32 ビット並んでいると読みにくいということもあるかと思うので、1 バイト (= 8 ビット) ごとに半角スペースを入れて表示するように変更してみます。

#!/bin/sh
hexdump -v -e '/4 "%08X" "\n"' ${1} | while read line; do
	echo "obase=2; ibase=16; ${line}" | bc | printf "%32s\n" $(cat) | sed -e 's/ /0/g' | sed -e 's/\(.\{8\}\)/\1 /g' | sed -e 's/ $//g'
done

少しコードが長くなりましたが、追加されたのは | sed -e 's/(.{8})/\1 /g' | sed -e 's/ $//g' の部分だけです。
これを実行してみると以下のようになります。

$ ./binfile2binary.sh /path/to/fib.bin
00000111 01000000 00000000 11101111
11111110 00000001 00000001 00010011
00000000 00010001 00101110 00100011
00000000 10000001 00101100 00100011
00000000 10010001 00101010 00100011
00000010 00000001 00000100 00010011
11111110 10100100 00100110 00100011
11111110 11000100 00100111 00000011
00000000 00010000 00000111 10010011
00000000 11100111 11000110 01100011
00000000 00010000 00000111 10010011
00000011 00000000 00000000 01101111
11111110 11000100 00100111 10000011
11111111 11110111 10000111 10010011
00000000 00000111 10000101 00010011
11111100 10011111 11110000 11101111
00000000 00000101 00000100 10010011
11111110 11000100 00100111 10000011
11111111 11100111 10000111 10010011
00000000 00000111 10000101 00010011
11111011 01011111 11110000 11101111
00000000 00000101 00000111 10010011
00000000 11110100 10000111 10110011
00000000 00000111 10000101 00010011
00000001 11000001 00100000 10000011
00000001 10000001 00100100 00000011
00000001 01000001 00100100 10000011
00000010 00000001 00000001 00010011
00000000 00000000 10000000 01100111
11111111 00000001 00000001 00010011
00000000 00010001 00100110 00100011
00000000 10000001 00100100 00100011
00000001 00000001 00000100 00010011
00000000 10100000 00000101 00010011
11110111 11011111 11110000 11101111
00000000 00000000 00000000 01101111

これで、8 文字ずつに半角スペースが追加されて読みやすくなりました 👏

具体的にどういう作業を追加したのかというと、まずsed -e 's/(.{8})/\1 /g' では 8 文字ごとに末尾に半角スペースを入れるようにしています。
さらに、8 文字ごとに末尾に半角スペースを入れると行末に余分な半角スペースが入ってしまうので、sed -e 's/ $//g' をすることで、行末の余分な半角スペースを削除しています。

sed を使って n 文字ごとに置換を行ったりする方法については以下の記事を参考にしてください。

3.4. コマンドとして登録しておく

使いたくなったときにすぐコマンド一つで使えるように、.bash_aliases.zsh_aliasesなどにエイリアスを追記しておくと便利です。

# 他のエイリアス
alias binfile2binary="/path/to/./binfile2binary.sh"
# 他のエイリアス

ちなみに、こうしておくと前提で書いた Vim のバイナリモードと同じように、以下のようにすればバイナリファイルを 2 進数で表示することができます。

$ vim -b fib.bin
:%!binfile2binary

逆変換はできないので、閉じる時は :q!として変更内容を破棄して閉じるようにしてください。

vim でバイナリファイルを 2 進数で表示
vim でバイナリファイルを 2 進数で表示

4. まとめ

このように hexdumpbcsed コマンドを活用することでバイナリファイルをうまく 2 進数に変換することができました 👏

Vim でも簡単にバイナリを読めるようになったので便利ですね 👍

シェルスクリプトは難しいですが、少しずつ使いこなせるようになりたいです…!

コメントを残す

メールアドレスが公開されることはありません。

CAPTCHA