xx.c in git
XX(1)                   FreeBSD General Commands Manual                  XX(1)

NAME
     xx – hexdump

SYNOPSIS
     xx [-arsz] [-c cols] [-g group] [-p count] [file]

DESCRIPTION
     xx dumps the contents of a file or standard input in hexadecimal format.

     The arguments are as follows:

     -a      Toggle ASCII output.

     -c cols
             Output cols bytes per line.  The default cols is 16.

     -g group
             Output extra space after every group bytes.  The default group is
             8.

     -p count
             Output a blank line after every count bytes.  count must be a
             multiple of cols.

     -r      Reverse hexdump.  Read hexadecimal input and write byte output.

     -s      Toggle offset output.

     -z      Skip output of lines containing only zeros.

SEE ALSO
     hexdump(1), xxd(1)

FreeBSD 12.0-RELEASE-p10       September 7, 2018      FreeBSD 12.0-RELEASE-p10
/* Copyright (C) 2017  C. McEnroe <june@causal.agency>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <ctype.h>
#include <err.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>

typedef unsigned char byte;

static bool zero(const byte *ptr, size_t size) {
	for (size_t i = 0; i < size; ++i) {
		if (ptr[i]) return false;
	}
	return true;
}

static struct {
	size_t cols;
	size_t group;
	size_t blank;
	bool ascii;
	bool offset;
	bool skip;
} options = { 16, 8, 0, true, true, false };

static void dump(FILE *file) {
	bool skip = false;

	byte buf[options.cols];
	size_t offset = 0;
	for (
		size_t size;
		(size = fread(buf, 1, sizeof(buf), file));
		offset += size
	) {
		if (options.skip) {
			if (zero(buf, size)) {
				if (!skip) printf("*\n");
				skip = true;
				continue;
			} else {
				skip = false;
			}
		}

		if (options.blank) {
			if (offset && offset % options.blank == 0) {
				printf("\n");
			}
		}

		if (options.offset) {
			printf("%08zX:  ", offset);
		}

		for (size_t i = 0; i < sizeof(buf); ++i) {
			if (options.group) {
				if (i && !(i % options.group)) {
					printf(" ");
				}
			}
			if (i < size) {
				printf("%02hhX ", buf[i]);
			} else {
				printf("   ");
			}
		}

		if (options.ascii) {
			printf(" ");
			for (size_t i = 0; i < size; ++i) {
				if (options.group) {
					if (i && !(i % options.group)) {
						printf(" ");
					}
				}
				printf("%c", isprint(buf[i]) ? buf[i] : '.');
			}
		}

		printf("\n");
	}
}

static void undump(FILE *file) {
	byte c;
	int match;
	while (0 < (match = fscanf(file, " %hhx", &c))) {
		printf("%c", c);
	}
	if (!match) errx(EX_DATAERR, "invalid input");
}

int main(int argc, char *argv[]) {
	bool reverse = false;
	const char *path = NULL;

	int opt;
	while (0 < (opt = getopt(argc, argv, "ac:g:p:rsz"))) {
		switch (opt) {
			break; case 'a': options.ascii ^= true;
			break; case 'c': options.cols = strtoul(optarg, NULL, 0);
			break; case 'g': options.group = strtoul(optarg, NULL, 0);
			break; case 'p': options.blank = strtoul(optarg, NULL, 0);
			break; case 'r': reverse = true;
			break; case 's': options.offset ^= true;
			break; case 'z': options.skip ^= true;
			break; default: return EX_USAGE;
		}
	}
	if (argc > optind) path = argv[optind];
	if (!options.cols) return EX_USAGE;

	FILE *file = path ? fopen(path, "r") : stdin;
	if (!file) err(EX_NOINPUT, "%s", path);

	if (reverse) {
		undump(file);
	} else {
		dump(file);
	}
	if (ferror(file)) err(EX_IOERR, "%s", path);

	return EX_OK;
}