16進テキストをバイナリーに変換する

"hexdump -C" といったコマンドでバイナリーファイルを 16 進数テキストに変換することはできるのに、その逆はないよねえという話になった。そしてたしかに知らない。あれば便利なのに…。

ということでみんな大好き再発明のお時間です。:

// hex2bin.cc
#include <algorithm> // std::find
#include <iterator>  // std::distance
#include <stdexcept> // std::runtime_error, std::exception
#include <cstdio>    // putchar, getchar
#include <iostream>  // std::cerr

template<typename T, size_t N> inline T* end_(T (&a)[N]) { return a + N; }

// input single charecter from stdin, discarding white-space.
inline int ch() {
  int c;
  do { c = getchar(); } while (9 == c || 10 == c || 12 == c || 32 == c);
  return c;
}

// convert hex charecter to int value 0..15
inline int from_(int h) {
  const char hex[] = "0123456789ABCDEFabcdef";
  const size_t d = std::distance(hex, std::find(hex, end_(hex), h));
  if (!(d < 22)) throw std::runtime_error("invalid hex charecter");
  return d < 16 ? d : d - 6;
}

// get value 0..255 from pair of character
inline int value() {
  const int i1 = ch(), i2 = ch();
  if (EOF == i1) return EOF;
  if (EOF != i2) return 16 * from_(i1) + from_(i2);
  throw std::runtime_error("missing last hex character");
}

int main() {
  try {
    int i;
    while ((i = value(), EOF != i)) putchar(i);
  } catch (const std::exception &e) {
    std::cerr << "ERROR: " << e.what() << std::endl;
  }
  return 0;
} 

こいつを次のようにビルドして…:

$ make hex2bin CXXFLAGS="-O2 -Wall"

動作確認。:

$ hexdump -e '16/1 "%02x " "\n"' hex2bin.cc | ./hex2bin
// hex2bin.cc
#include <algorithm> // std::find
#include <iterator>  // std::distance
#include <stdexcept> // std::runtime_error, std::exception
#include <cstdio>    // putchar, getchar
#include <iostream>  // std::cerr

template<typename T, size_t N> inline T* end_(T (&a)[N]) { return a + N; }

// input single charecter from stdin, discarding white-space.
inline int ch() {
  int c;
  do { c = getchar(); } while (9 == c || 10 == c || 12 == c || 32 == c);
  return c;
}

// convert hex charecter to int value 0..15
inline int from_(int h) {
  const char hex[] = "0123456789ABCDEFabcdef";
  const size_t d = std::distance(hex, std::find(hex, end_(hex), h));
  if (!(d < 22)) throw std::runtime_error("invalid hex charecter");
  return d < 16 ? d : d - 6;
}

// get value 0..255 from pair of character
inline int value() {
  const int i1 = ch(), i2 = ch();
  if (EOF == i1) return EOF;
  if (EOF != i2) return 16 * from_(i1) + from_(i2);
  throw std::runtime_error("missing last hex character");
}

int main() {
  try {
    int i;
    while ((i = value(), EOF != i)) putchar(i);
  } catch (const std::exception &e) {
    std::cerr << "ERROR: " << e.what() << std::endl;
  }
  return 0;
} 


hexdump の書式文字列の指定方法が、ちょっと難しいよなー…。覚えられると便利そうなんだけど。

与えているのは '16/1 "%02x " "\n"' という書式で、

  • 16/1 は、続くパターンを 1 バイトごと 16 回繰り返す指定
  • "%02x" で 16 進数を二桁で表示、一桁になる場合は 0 で埋める指定
  • "\n" は改行出力指定

つまり 16 回、 1 バイトを %02x の書式で出力して区切りの改行を入れる、というパターンを実行している。