16進数文字列をバイナリデータに変換する

最近、割と16進数文字列で表現されたデータに遭遇することが多くなったので、
perlでうんぬんやるのもいいんだけど、やってることは、2文字取り出して数値へ変換しているだけなんで、
C言語でも簡単にかけそうだな、と思ったわけです。
調べれば有用なソフトはいくつでも見つかりそうですが、自分で書きたいから書きました。
以下そのコード。


コード

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE (4096)

int main(int argc, char **argv){
	int buffer_size = BUFFER_SIZE;
	int len, i, c;
	unsigned char sum;
	char *buffer;
	char *in_file = NULL;
	char *out_file = NULL;
	FILE *in = stdin;
	FILE *out = stdout;
	
	if(argc > 1){
		if(memcmp(argv[1], "-h", 3) == 0){
			fprintf(stderr, "usage: hex2bin [OPTION] FILE or hex2bin < IN [ > OUT]\n");
			fprintf(stderr, "OPTION:\n"
							"    -s [size]     : buffer size. (default: %dbyte)\n"
							"    -i [IN] : input file path. (if you use STDIN, do not use this option)\n"
							"    -o [OUT] : output file path.\n", BUFFER_SIZE);
			fprintf(stderr, "IN : input file path.\n");
			fprintf(stderr, "OUT: output file path.\n");
			return 0;
		}
		// arg
		while(argc > 2){
			if(memcmp(argv[1], "-s", 3) == 0){
				buffer_size = atoi(argv[2]);
				argv+=2;
				argc--;
			}else if(memcmp(argv[1], "-o", 3) == 0){
				out_file = argv[2];
				if((out=fopen(out_file, "wb")) == NULL){
					fprintf(stderr, "can't open file: %s\n", out_file);
					return -1;
				}
				argv+=2;
				argc--;
			}else if(memcmp(argv[1], "-i", 3)==0){
				in_file = argv[2];
				argv+=2;
				argc--;
			}
			argc--;
		}
		
		if(in_file == NULL && argc>1) in_file = argv[1];
		
		if(in_file!=NULL){
			if((in=fopen(in_file, "rb")) == NULL){
				fprintf(stderr, "can't open file: %s\n", in_file);
				return -1;
			}
		}
	}
	
	if(buffer_size < 1) buffer_size = BUFFER_SIZE;
	if((buffer = (char *)malloc(buffer_size)) == NULL){
		fprintf(stderr, "Buffer allocation failed : size=%d\n", buffer_size);
		return -1;
	}

/*
	fprintf(stderr, "buffer size: %dbyte\n", buffer_size);
	if(in != stdin) fprintf(stderr, "input: %s\n", in_file);
	if(out != stdout) fprintf(stderr, "output: %s\n", out_file);
*/

	c=0; sum=0;
	while(len = fread(buffer, 1, buffer_size, in)){
		for(i=0; i<len; ++i){
			if(buffer[i] >='0' && buffer[i] <= '9'){
				sum *= 16;
				sum += buffer[i] -'0';
				c++;
			}else if(buffer[i] >='A' && buffer[i] <= 'F'){
				sum *= 16;
				sum += buffer[i] -'A'+10;
				c++;
			}else if(buffer[i] >='a' && buffer[i] <= 'f'){
				sum *= 16;
				sum += buffer[i]-'a'+10;
				c++;
			}
			
			if(c==2){
				fwrite(&sum, 1, 1, out);
				c=0; sum=0;
			}
		}
	}
	
	fclose(in);
	fclose(out);
	return 0;
}

このプログラムには見ての通り欠点がいくつかあります。最後のほうに書きます。


本体はこちら。自己責任でどうぞ。
http://www.rying.net/arc/hex2bin.exe

つかいかた

hexdata.txtに16進数字のみで書いたとして、
hex2bin < hexdata.txt とやると、標準出力にバイナリデータが得られます。
hex2bin < hexdata.txt > a.bin とやると、a.binに書き出されます。
hex2bin -h で簡単なコマンドラインの説明っぽいのがでます。


また、オプションに、-s, -i, -oを用意しています。
-s: バッファサイズを指定。デフォルトで4096あるので、あまりいじる必要はないです。
-i: 入力ファイルを指定。標準入力を使う場合は指定しないこと。
-o: 出力ファイルを指定。


hex2bin < hexdata.txt > a.bin
hex2bin -i hexdata.txt -o a.bin
hex2bin -o a.bin hexdata.txt
これらは同じ処理を行うはずです。

欠点

このプログラムはファイルの先頭から読み取っていき、
16進数文字を2個得られたら出力しているだけです。なので、それ以外の文字は無視します。
これに適合しないデータはフォーマットが悪いということにします。
つまり、入力に適したデータは、
16進数文字のみで構成されており、適度に空白や改行で分かりやすくなっているもの
に限られます。

主な欠点

1x2-3 45 とデータが書かれていた場合、 0x12 0x34が出力される。
0x00などには対応しない。(0xの0の部分も16進数文字と解釈して読み取ってしまう。)
\x00は大丈夫です。(\は16進数文字でないので)
他に問題がありそうです。特に引数周りの処理は初めてやってみたのでどうなることやら。

0xの対策

0の次にxが出てきたら、カウンターをリセットすればよさそうです。

			}else if(buffer[i] >='a' && buffer[i] <= 'f'){
				sum *= 16;
				sum += buffer[i]-'a'+10;
				c++;
			}

のあとにでも、

			else if(buffer[i] >='x'){
				c=0; sum=0;
			}

とか。
試してないんで大丈夫かわからんけども。