Search a element in file and print whole line - c

I have a file with these contents:
Code Name Income Allow Pens Ins Depend Charity Taxable Tax Net
------------------------------------------------------------------------------------------------------------------------
008 John 100000 4000 5000 1000 3200 1000 85800 20280 79720
001 Doe 50000 4000 0 500 1600 0 43900 7725 42275
I want to print a record if the input code is same as the code in the file.
This is my code:
fscanf(fp, " %3d%s%lf%lf%lf%lf%lf%lf%lf%lf%lf", &code_t, buffer, &inc_t, &personal, &pension_t, &health_t, &depend_t, &gift_t, &taxable_t, &tax_t, &net_t);
printf("\n");
printf(" 03d%20s%11.0lf%9.0lf%10.0lf%8.0lf%10.0lf%11.0lf%11.0lf%10.0lf%10.0lf\n", code_t, buffer, inc_t, personal, pension_t, health_t, depend_t, gift_t, taxable_t, tax_t, net_t);
`
but it doesn't work.
So I'm thinking about using fscanf to read only the code and then print the line which contains that code. But how can I read the code without other contents like Name, Income, ... and how to read if the code has leading 0 like that?

First you cannot read the file with its header using the scanf you give because Code is incompatible with the first %d, so you need to bypass the first 2 lines
Warning a % is missing in your printf to print the code, so the code is considered as the string by %s with an unspecified behavior (a crash generally)
But how can I read the code without other contents like Name, Income
Of course if you use your scanf you also read the other fields, is it a real problem ? You can also read line per line as a strings (fgets or getline) and look at the beginning if you have the expected code and in that case manage the rest of the string to extract the fields if needed etc
An other way if the content of the file is very formatted is to change the file pointer using fseek to only read the codes up to the expected one (see the proposal at the end of my answer).
how to read if the code has leading 0 like that?
I do not understand scanf read it well, this is not octal because there is 008. If the presence of the 0 at the left are important do not manage the code as a number but as a string both in the file and when the code to search is given
A code from yours reading well your input file :
#include <stdio.h>
int bypassLine(FILE * fp)
{
int c;
for (;;) {
c = fgetc(fp);
if (c == EOF)
return 0;
if (c == '\n')
return 1;
}
}
int main()
{
FILE * fp = stdin;
int code_t;
char buffer[64];
double inc_t, personal, pension_t, health_t, depend_t, gift_t, taxable_t, tax_t, net_t;
if (!bypassLine(fp) || !bypassLine(fp))
puts("too short file");
else {
while (fscanf(fp, " %3d%s%lf%lf%lf%lf%lf%lf%lf%lf%lf", &code_t, buffer, &inc_t, &personal, &pension_t, &health_t, &depend_t, &gift_t, &taxable_t, &tax_t, &net_t) == 11) {
printf(" %03d%20s%11.0lf%9.0lf%10.0lf%8.0lf%10.0lf%11.0lf%11.0lf%10.0lf%10.0lf\n", code_t, buffer, inc_t, personal, pension_t, health_t, depend_t, gift_t, taxable_t, tax_t, net_t);
}
}
}
Compilation and execution :
pi#raspberrypi:/tmp $ gcc -pedantic -Wextra f.c
pi#raspberrypi:/tmp $ cat f
Code Name Income Allow Pens Ins Depend Charity Taxable Tax Net
------------------------------------------------------------------------------------------------------------------------
008 John 100000 4000 5000 1000 3200 1000 85800 20280 79720
001 Doe 50000 4000 0 500 1600 0 43900 7725 42275
pi#raspberrypi:/tmp $ ./a.out < f
008 John 100000 4000 5000 1000 3200 1000 85800 20280 79720
001 Doe 50000 4000 0 500 1600 0 43900 7725 42275
pi#raspberrypi:/tmp $
Note there is no protection against an overflow when reading the string in scanf with just %s, if is better to use %63s because I sized buffer 64
A little change to search for the code, still using your scanf, giving the name of the file and the expected code in argument :
#include <stdio.h>
int bypassLine(FILE * fp)
{
int c;
for (;;) {
c = fgetc(fp);
if (c == EOF)
return 0;
if (c == '\n')
return 1;
}
}
int main(int argc, char ** argv)
{
if (argc != 3)
printf("usage : %s <file> <code>\n", *argv);
else {
FILE * fp;
int code_t, expected;
char buffer[64];
double inc_t, personal, pension_t, health_t, depend_t, gift_t, taxable_t, tax_t, net_t;
if ((fp = fopen(argv[1], "r")) == NULL) {
fprintf(stderr, "cannot open '%f'\n", argv[1]);
return -1;
}
if (!bypassLine(fp) || !bypassLine(fp)) {
fprintf(stderr, "too short file '%s'\n", argv[1]);
fclose(fp);
return -1;
}
if (sscanf(argv[2], "%d%c", &expected, buffer) != 1) {
fprintf(stderr, "invalid code '%s'\n", argv[2]);
}
else {
while (fscanf(fp, " %3d%63s%lf%lf%lf%lf%lf%lf%lf%lf%lf", &code_t, buffer, &inc_t, &personal, &pension_t, &health_t, &depend_t, &gift_t, &taxable_t, &tax_t, &net_t) == 11) {
if (code_t == expected) {
printf(" %03d%20s%11.0lf%9.0lf%10.0lf%8.0lf%10.0lf%11.0lf%11.0lf%10.0lf%10.0lf\n", code_t, buffer, inc_t, personal, pension_t, health_t, depend_t, gift_t, taxable_t, tax_t, net_t);
fclose(fp);
return 0;
}
}
fprintf(stderr, "code %d not found in '%s'\n", expected, argv[1]);
}
fclose(fp);
return -1;
}
}
Compilation and execution :
pi#raspberrypi:/tmp $ gcc -pedantic -Wextra f.c
pi#raspberrypi:/tmp $ ./a.out ./f 2
code 2 not found in './f'
pi#raspberrypi:/tmp $ ./a.out ./f 8
008 John 100000 4000 5000 1000 3200 1000 85800 20280 79720
pi#raspberrypi:/tmp $
An other way using fseek to move directly from code to code in the file :
#include <stdio.h>
int bypassLine(FILE * fp)
{
int c;
for (;;) {
c = fgetc(fp);
if (c == EOF)
return 0;
if (c == '\n')
return 1;
}
}
int main(int argc, char ** argv)
{
if (argc != 3)
printf("usage : %s <file> <code>\n", *argv);
else {
FILE * fp;
int code_t, expected;
char buffer[64];
double inc_t, personal, pension_t, health_t, depend_t, gift_t, taxable_t, tax_t, net_t;
if ((fp = fopen(argv[1], "r")) == NULL) {
fprintf(stderr, "cannot open '%f'\n", argv[1]);
return -1;
}
if (!bypassLine(fp) || !bypassLine(fp)) {
fprintf(stderr, "too short file '%s'\n", argv[1]);
fclose(fp);
return -1;
}
if (sscanf(argv[2], "%d%c", &expected, buffer) != 1) {
fprintf(stderr, "invalid code '%s'\n", argv[2]);
}
else {
long offset = ftell(fp);
while (fscanf(fp, " %03d", &code_t) == 1) {
if (code_t == expected) {
/* extract the other fields */
if (fscanf(fp, "%63s%lf%lf%lf%lf%lf%lf%lf%lf%lf", buffer, &inc_t, &personal, &pension_t, &health_t, &depend_t, &gift_t, &taxable_t, &tax_t, &net_t) == 10) {
printf(" %03d%20s%11.0lf%9.0lf%10.0lf%8.0lf%10.0lf%11.0lf%11.0lf%10.0lf%10.0lf\n", code_t, buffer, inc_t, personal, pension_t, health_t, depend_t, gift_t, taxable_t, tax_t, net_t);
fclose(fp);
return 0;
}
else {
fprintf(stderr, "code %d found but cannot read next fields\n", code_t);
fclose(fp);
return -1;
}
}
/* the lines are supposed having all the times 114 characters newline included */
offset += 114;
if (fseek(fp, offset, SEEK_SET) == -1) {
fprintf(stderr, "error when going at offset %d of '%s'\n", offset, argv[1]);
fclose(fp);
return -1;
}
}
fprintf(stderr, "code %d not found in '%s'\n", expected, argv[1]);
}
fclose(fp);
return -1;
}
}
Compilation and execution :
pi#raspberrypi:/tmp $ gcc -pedantic -Wextra f.c
pi#raspberrypi:/tmp $ cat f
Code Name Income Allow Pens Ins Depend Charity Taxable Tax Net
------------------------------------------------------------------------------------------------------------------------
008 John 100000 4000 5000 1000 3200 1000 85800 20280 79720
001 Doe 50000 4000 0 500 1600 0 43900 7725 42275
pi#raspberrypi:/tmp $ ./a.out ./f 8
008 John 100000 4000 5000 1000 3200 1000 85800 20280 79720
pi#raspberrypi:/tmp $ ./a.out ./f 1
001 Doe 50000 4000 0 500 1600 0 43900 7725 42275
pi#raspberrypi:/tmp $ ./a.out ./f 11
code 11 not found in './f'

I want to print a record if the input code is same as the code in the file.
If your goal is simply to print the records (aka lines) where "Code" matches some value given by the user, your approach seems a bit too complex as there is no need for scanning all the fields.
Simply use fgets to read the line, then check the Code value and do the print if it matches.
Something like:
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
int main(int argc, char* argv[]){
if (argc != 2)
{
printf("Wrong usage...\n");
return 1;
}
int code_to_print = atoi(argv[1]);
int code_read;
FILE* fp = fopen("db.txt", "r");
if (!fp)
{
printf("File error...\n");
return 1;
}
char buf[1024];
while (fgets(buf, sizeof buf, fp))
{
if (sscanf(buf, "%d", &code_read) == 1 && code_read == code_to_print)
{
printf("%s", buf);
}
}
fclose(fp);
}
Use the program like:
./prog 8
.. how to read if the code has leading 0 like that?
If the leading zeros are important then you can't scan using %d as that will "remove" the zeros. Instead you need to scan the Code as a word. Like:
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
int main(int argc, char* argv[]){
if (argc != 2)
{
printf("Wrong usage...\n");
return 1;
}
char code_read[4] = {0};
FILE* fp = fopen("db.txt", "r");
char buf[1024];
while (fgets(buf, sizeof buf, fp))
{
if (sscanf(buf, "%3s", code_read) == 1 && strcmp(code_read, argv[1]) == 0)
{
printf("%s", buf);
}
}
fclose(fp);
}
Use the program like:
./prog 008

Related

Copying file and write the info of file backwards

Need create a copy of the file that was previously created with fifteen random numbers inside, and in this copy write those numbers backwards.
I tried to read the file with for() and write to the array, which will also fill in the newly created file with for(), but the IDE stops the script as soon as it goes to for().
#include <stdio.h>
#include <stdlib.h>
int main(){
char a[20], sf[20], tf[20];
FILE *source, *target;
int i;
printf("Enter name of file to copy\n");
gets(sf);
source = fopen(sf, "r");
if (source == NULL){
printf("Press any key to exit...\n");
exit(1);
}
printf("Enter name of target file\n");
gets(tf);
target = fopen(tf, "w");
if (target == NULL){
fclose(source);
printf("Press any key to exit...\n");
exit(1);
}
for(i=0;i<15;i++){
fscanf(source, "%d", a[i]);
//printf("%d\n", a[i]);
}
for(i=15;i>0;i--){
fprintf(target, "%d\n", a[i]);
}
//printf("File copied successfully.\n");
return 0;
}
Some remarks :
as it is said in remark you read int so you need an array of int, not an array of char to save them, currently you will write out of a with an undefined behavior
if you need to save 15 values an array for 15 is enough, 20 (supposing int) is useless
never use gets, it is deprecated (since years) because dangerous, use fgets to not take the risk to write out of the receiving string, and do not forget to remove the probable newline
may be the input and output files will be the same, so read first before to open output file and write its contents
fscanf(source, "%d", a[i]); is invalid and must be fscanf(source, "%d", &a[i]);
check the result of fscanf to manage error cases in the input file
fprintf(target, "%d\n", a[i]); must be fprintf(target, "%d\n", a[i-1]);
to explicitly fclose the output file is better in case you later transform your program to something more complex
A proposal :
#include <stdio.h>
#include <string.h>
#define N 15
FILE * openIt(const char * msg, const char * dir)
{
char fn[256];
printf(msg);
if (fgets(fn, sizeof(fn), stdin) == NULL)
/* EOF */
return NULL;
char * p = strchr(fn, '\n');
if (p != NULL)
*p = 0;
FILE * fp = fopen(fn, dir);
if (fp == NULL)
fprintf(stderr, "cannot open '%s'\n", fn);
return fp;
}
int main()
{
FILE * fp;
if ((fp = openIt("Enter name of file to copy\n", "r")) == NULL)
return -1;
int a[N], i;
for (i = 0; i != N; ++i) {
if (fscanf(fp, "%d", &a[i]) != 1) {
fprintf(stderr, "invalid integer rank %d\n", i);
fclose(fp);
return -1;
}
}
fclose(fp);
if ((fp = openIt("Enter name of target file\n", "w")) == NULL)
return -1;
while (i--) {
fprintf(fp, "%d\n", a[i]);
}
fclose(fp);
return 0;
}
Compilation and execution :
pi#raspberrypi:/tmp $ gcc -pedantic -Wall -Wextra r.c
pi#raspberrypi:/tmp $ cat in
1 2 3 4 5
6 7 8 9 10
11 12
13
14
15
not read
pi#raspberrypi:/tmp $ ./a.out
Enter name of file to copy
in
Enter name of target file
out
pi#raspberrypi:/tmp $ cat out
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
pi#raspberrypi:/tmp $ ./a.out
Enter name of file to copy
out
Enter name of target file
out
pi#raspberrypi:/tmp $ cat out
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pi#raspberrypi:/tmp $

fscanf returns 3 instead of -1 (EOF) at the end of the file

So there's a file I'm using fscanf() in. I've set a condition in my code that when (fscanf(...) == EOF, the program needs to break out of the function I'm currently in. The thing is, this condition is never satisfied in any of the cases where there's no more lines of text in the file. EOF is always -1, whereas fscanf(...) returns 4 each time there's a line of code, and 3 when there's nothing left for it to search through (instead of -1). If I add a line of code similar to the other ones, I will simply get one more instance of fscanf() returning 4, and then again, it'll give me a 3.
What could possibly be the issue? Thank you in advance!
Sample text file content:
CHRISTOU GIORGOS,140,VAS. OLGAS 112
MALAKOU MALAKOS,150,DRAS. BAGAS 12
TSIKOU GIJRAN,140,JABS. DRALGAS 1
TSIKOU BIRBAN,140,JABS. DRALGAS 1
DELHDHMHTRIOU SPYROS,50,SPEED. BAGAS 62
FOX SIN,40,BAN. NINJA 1
Code:
#include <stdio.h>
#define M 100
typedef struct {
char name[30];
int apousies;
} studentT;
void readInput (FILE* infile, studentT students[], int *pApousies, int *pStudents);
int main()
{
char inputfilename[30];
FILE* infile;
while (1) {
printf("Input file name :");
gets(inputfilename);
infile = fopen(inputfilename, "r");
if (infile != NULL) break;
printf("Cannot open input file %s. Try again.\n", inputfilename);
}
studentT students[M];
int numberOfStudents = 0, numberOfApousies = 0;
readInput(infile, students, &numberOfApousies, &numberOfStudents);
fclose(infile);
return 0;
}
void readInput (FILE* infile, studentT students[], int *pApousies, int *pStudents)
{
int nscan, apousies, studcount, apouscount, line;
char name[30], comments[68], termch;
line = 0;
while (1)
{
nscan = fscanf(infile, "%30[^,], %d, %68[^\n]%c", name, &apousies, comments, &termch);
/* printf("onoma: %s apousies: %d sxolia: %s terma: %c\n", name, apousies, comments, termch);
printf("%d\n", nscan);
printf("%d\n", EOF);*/
if (nscan == EOF) break;
line++;
if (nscan != 4 || termch != '\n')
{
printf("Error in line %d. Program termination\n", line);
exit(1);
}
}
}
fscanf returns 3 instead of -1 (EOF) at the end of the file
Because the last line lacks a '\n'.
OP's code "works" with the "tmp.txt" the below code makes.
fscanf() is hard to use right. Easier to code and debug with fgets(). Discussion follows.
"%30[^,]" allows too much for char name[30]. Use char name[30+1] or "%29[^,]"
OP's approach can readily fail with seemingly minor parsing problems such as a missing '\n' on the last line. After such a failure, recovery is extraordinary difficult with fscanf()
Debug: Importantly, the below print should not be attempted until code insures nscan >= 4
if (nscan >= 4) // add
printf("onoma: %s apousies: %d sxolia: %s terma: %c\n", name, apousies, comments, termch);
Instead, use fgets(). With line orientated data, this really is the best first step.
fscanf() is challenging to use and cope with errors. Far simpler to read a line with fgets() and then parse.
Using " %n" is a nice way to detect if all the line parsed.
#include <stdio.h>
#include <stdlib.h>
#define M 100
typedef struct {
char name[30];
int apousies;
} studentT;
void readInput(FILE* infile, studentT students[], int *pApousies,
int *pStudents) {
(void) students;
(void) pApousies;
(void) pStudents;
int line = 0;
char buf[200];
while (fgets(buf, sizeof buf, infile)) {
int apousies;
char name[30], comments[68];
int n = 0;
line++;
sscanf(buf, " %29[^,],%d , %67[^\n] %n", name, &apousies, comments, &n);
if (n == 0 || buf[n]) {
fprintf(stderr, "Error in line %d <%s>. Program termination\n", line, buf);
exit(1);
}
printf("Success %d <%s> %d <%s>\n", line, name, apousies, comments);
}
}
Sample use
int main() {
FILE *f = fopen("tmp.txt", "w");
fputs("CHRISTOU GIORGOS,140,VAS. OLGAS 112\n"
"MALAKOU MALAKOS,150,DRAS. BAGAS 12\n"
"TSIKOU GIJRAN,140,JABS. DRALGAS 1\n"
"TSIKOU BIRBAN,140,JABS. DRALGAS 1\n"
"DELHDHMHTRIOU SPYROS,50,SPEED. BAGAS 62\n"
"FOX SIN,40,BAN. NINJA 1\n", f);
fclose(f);
f = fopen("tmp.txt", "r");
studentT st[M];
readInput(f, st, NULL, NULL);
fclose(f);
}
Output
Success 1 <CHRISTOU GIORGOS> 140 <VAS. OLGAS 112>
Success 2 <MALAKOU MALAKOS> 150 <DRAS. BAGAS 12>
Success 3 <TSIKOU GIJRAN> 140 <JABS. DRALGAS 1>
Success 4 <TSIKOU BIRBAN> 140 <JABS. DRALGAS 1>
Success 5 <DELHDHMHTRIOU SPYROS> 50 <SPEED. BAGAS 62>
Success 6 <FOX SIN> 40 <BAN. NINJA 1>

C: How to read a text file and grab certain values after a certain point?

So basically the text file would look like this
Starting Cash: 1500
Turn Limit (-1 for no turn limit): 10
Number of Players Left To End Game: 1
Property Set Multiplier: 2
Number of Houses Before Hotels: 4
Must Build Houses Evenly: Yes
Put Money In Free Parking: No
Auction Properties: No
Salary Multiplier For Landing On Go: 1
All I need from the file is basically anything after ":"
I'm just confused how to only read anything after a ":"?
This is what I have right now. I just can't seem to think of a way to only scan for the numbers/yesorno.
void readRules(char*file_name)
{
Rules r;
FILE *file = NULL;
file = fopen(file_name, "r");
if (file == NULL) {
printf("Could not open %s\n", file_name);
return;
}
char c=fgetc(file);
fscanf(file, "%c", &c);
while (!feof(file))
{
fscanf(file, "%c", &c);
if(c==':')
{
r.startCash=c;
}
}
printf("There are %c word(s).\n", r.startCash);
fclose(file);
}
Thank you.
This program will read integers following a colon in each line of the file given. I imagine this is appropriate? You also have some strings after colons. If you want to read those, you can try scanning for a string "%s" and testing if the function returns nonzero (for at least one format pattern matched).
#include <stdio.h>
#include <stdlib.h>
#define MAXLINE 1000
void readRules (const char *filename) {
FILE *fp;
char *lp, line[MAXLINE];
int n;
// Return early if file cannot be opened.
if ((fp = fopen(filename, "r")) == NULL) {
fprintf(stderr, "Error: Couldn't open \"%s\"!\n", filename);
return;
}
// Use fgets to read consecutive lines. Returns NULL on error or EOF.
while (fgets(line, MAXLINE, fp) != NULL) {
// Read until newline is hit or buffer size exceeded.
for (lp = line; *lp != '\n' && (lp - line) < MAXLINE; lp++) {
// If encounter colon and sccanf reads at least 1 integer..
if (*lp == ':' && sscanf(lp + 1, "%d", &n) == 1) {
fprintf(stdout, "%d\n", n);
break;
}
}
}
// Clean up.
fclose(fp);
}
int main (int argc, const char *argv[]) {
readRules("test.txt");
return 0;
}
When run with your example input, it produces:
1500
10
1
2
4
1

Converting strings and ints from input file and printing to output file

I am trying to convert strings and integers to binary using fscanf and fwrite to write them to an output file.
My input file:
a 100
ab 99
abc 98
abcd 97
abcde 96
Each space separating the string and int on each line is a tab.
Here is my main.c file where the magic should be happening (but is not):
#include <stdio.h>
#include <stdlib.h>
#define MAX_LEN 30
int main(int argc, char * argv[]){
FILE *ifp;
FILE *ofp;
if(argc != 4){
fprintf(stderr, "Usage: flag %s input-file output-file\n", argv[0]); exit(1);
}
if((ifp = fopen(argv[2], "r")) == NULL){ /* error check to make sure the input file is open*/
fprintf(stderr, "Could not open file: %s", argv[2]); exit(1);
}
puts("input file open\n");
if((ofp = fopen(argv[3], "wb")) == NULL){ /* Opens output file to write in binary*/
puts("couldnt open output file\n"); exit(1);
}
puts("output file open\n");
unsigned char tempstr[MAX_LEN];
unsigned int tempint;
while(fscanf(ifp, "%u %u\n",(unsigned int*)&tempstr, &tempint) == 2){
fwrite((const void*)&tempstr, sizeof(tempstr)+1, 1, ofp);
fwrite((const void*)&tempint, sizeof(unsigned int), 1, ofp);
puts("ran loop");
}
fclose(ifp);
return 0;
}
When I run my code my while loop does not seem to be running(ie.the "ran loop" is not being output). Not sure where to go from here?
Also my calls at the command line are as follows:
./a.out main.c t1.txt t1.bin
Any help would be appreciated! Thanks!
I believe this should get rid of the seg fault.
The type should be char * instead of unsigned char *
The array should have enough space to hold the required characters plus the null terminator.
.
char tempstr[30];
unsigned int tempint;
while (fscanf(ifp, "%s \t %i\n",tempstr, &tempint) == 2 ) {
fwrite((const void*)&tempstr, sizeof(tempstr)+1, 1, ofp);
fwrite((const void*)&tempint, sizeof(unsigned int), 1, ofp);
puts("ran loop");
}
Also I guess you don't need main.c, while executing ./a.out ... etc. You can change check for argc != 3 instead of 4 in your code.

hexdump output vs xxd output

I'm trying to create a hexdump like xxd but there are some differences that I'm trying to resolve. Currently the program processes 10 characters per line as seen on the utmost right column vs 16 in xxd. It also only shows 1 octet per column instead of pairs of 2 octet's.
xxd
0000000: 2369 6e63 6c75 6465 203c 7374 6469 6f2e #include <stdio.
my output
0: 23 69 6E 63 6C 75 64 65 20 3C #include <
EDIT:
To add some clarification, I am trying to achieve two things.
1) I would like this program to output exactly like xxd. For this it needs to output 32 Hex numbers (8x columns of 4).
2) I would also like the program to list the hex numbers in row's columns of 4 like in xxd.
I've tried to edit the "10" in the source below to something like "12" but it creates errors in the output, it seems to be a magic number.
source:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#define BYTE_OFFSET_INIT 8
#define CHAR_OFFSET_INT 39
#define LINE_LENGTH 50
static void print_e(int e, char *program, char *file)
{
fprintf(stderr, "%s: %s: %s\n", program, file, strerror(e));
}
static void print_line(char *line)
{
int i;
/* sprintf leaves terminators mid-line, so clear them out so we can print the full line */
for (i = BYTE_OFFSET_INIT; i < CHAR_OFFSET_INT; i++)
if (line[i] == '\0')
line[i] = ' ';
printf("%s\n", line);
}
int main(int argc, char *argv[])
{
char line[LINE_LENGTH + 1];
int ch;
int character = 0;
int line_offset = 0;
int byte_offset = BYTE_OFFSET_INIT, char_offset = CHAR_OFFSET_INT;
if (argc != 2) {
fprintf(stderr, "Usage: %s [file]\n", argv[0]);
exit(EXIT_FAILURE);
}
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
print_e(errno, argv[0], argv[1]);
exit(EXIT_FAILURE);
}
printf("Offset Bytes Characters\n");
printf("------ ----------------------------- ----------\n");
while ((ch = fgetc(fp)) != EOF) {
if (character == 0) {
sprintf(line, "%6d ", line_offset);
line_offset += 10;
}
sprintf(line + byte_offset, "%02X ", ch);
sprintf(line + char_offset, "%c", isprint(ch) ? ch : '.');
character++;
char_offset++;
byte_offset += 3;
if (character == 10) {
print_line(line);
character = 0;
char_offset = CHAR_OFFSET_INT;
byte_offset = BYTE_OFFSET_INIT;
}
}
if (ferror(fp)) {
print_e(errno, argv[0], argv[1]);
exit(EXIT_FAILURE);
}
if (character > 0)
print_line(line);
if (fclose(fp) == EOF) {
print_e(errno, argv[0], argv[1]);
exit(EXIT_FAILURE);
}
return 0;
}
While it's possible to scan one byte at a time and write it into the output string at the correct position, it is by no means necessary. It is way easier to read DISPLAY_LENGTH bytes at once and loop over the thus read number of bytes twice; first outputting the hex representation, then again for the ASCII characters. The only (minor) caveat is what to do at the end of the file; but since fread returns the number of characters, you can just keep on counting and output spaces for as long as necessary to fill the hex line.
This leads to the following program. DISPLAY_LENGTH is the total number of bytes to display per line, GROUP_BYTES is the number of single bytes in each hexadecimal group (setting it to 1 will display a 'regular' spaced hex output, 2 will group as in your xxd example, and higher values should also work).
I had some fun figuring out the magic formulae to correctly center the text Bytes and calculating how many dashes to display for the separator. The rest is very straightforward.
I don't know what xxd output looks like, apart from your one-line example, so I use stat to read out the length of the file in advance (with an added opportunity to display an error for "not a file" -- try for example with a folder) and display the correct number of dashes and spaces to line up the line counter. I set this value to a minimum of 6 so there is always room for the text Offset.
If your compiler is not a modern one, it may complain about the %zu format string. If so, use %lu; you may also need to change all occurrences of size_t to unsigned long.
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#define DISPLAY_LENGTH 21
#define GROUP_BYTES 2
static void print_e(int e, char *program, char *file)
{
fprintf(stderr, "%s: %s: %s\n", program, file, strerror(e));
}
int main(int argc, char *argv[])
{
size_t i;
struct stat fs;
int n_digit;
unsigned char read_buf[DISPLAY_LENGTH];
size_t bytes_read, cpos = 0;
if (argc != 2)
{
fprintf(stderr, "Usage: %s [file]\n", argv[0]);
exit(EXIT_FAILURE);
}
FILE *fp = fopen(argv[1], "rb");
if (!fp)
{
print_e (errno, argv[0], argv[1]);
exit(EXIT_FAILURE);
}
if (stat(argv[1], &fs) == -1)
{
print_e (errno, argv[0], argv[1]);
exit(EXIT_FAILURE);
}
if ((fs.st_mode & S_IFMT) != S_IFREG) /* regular file */
{
fprintf(stderr, "Not a regular file: %s\n", argv[1]);
exit(EXIT_FAILURE);
}
n_digit = 0;
while (fs.st_size > 0)
{
fs.st_size /= 10;
n_digit++;
}
if (n_digit < 6)
n_digit = 6;
printf("%*s ", n_digit, "Offset");
printf("%*s%-*s", ((2*DISPLAY_LENGTH+(DISPLAY_LENGTH+GROUP_BYTES-1)/GROUP_BYTES)+2)/2, "Bytes", ((2*DISPLAY_LENGTH+(DISPLAY_LENGTH+GROUP_BYTES-1)/GROUP_BYTES)+2-5)/2, "");
printf (" Characters\n");
for (i=0; i<n_digit; i++)
printf ("-");
printf(" ");
for (i=1; i<2*DISPLAY_LENGTH+(DISPLAY_LENGTH+GROUP_BYTES-1)/GROUP_BYTES; i++)
printf ("-");
printf (" ");
for (i=0; i<DISPLAY_LENGTH; i++)
printf ("-");
printf ("\n");
while ( (bytes_read = fread (read_buf, 1, DISPLAY_LENGTH, fp)))
{
printf ("%*zu ", n_digit, cpos);
for (i=0; i<bytes_read; i++)
{
if (!(i % GROUP_BYTES))
printf (" ");
printf ("%02X", read_buf[i]);
}
while (i < DISPLAY_LENGTH)
{
if (!(i % GROUP_BYTES))
printf (" ");
printf (" ");
i++;
}
printf (" ");
for (i=0; i<bytes_read; i++)
printf ("%c", isprint(read_buf[i]) ? read_buf[i] : '.');
printf ("\n");
cpos += bytes_read;
}
if (ferror(fp))
{
print_e (errno, argv[0], argv[1]);
exit(EXIT_FAILURE);
}
if (fclose(fp))
{
print_e (errno, argv[0], argv[1]);
exit(EXIT_FAILURE);
}
return 0;
}
Sample output, displaying its own compiled executable with a display length of 21 and grouped per 2 bytes:
Offset Bytes Characters
------ ---------------------------------------------------- ---------------------
0 CFFA EDFE 0700 0001 0300 0080 0200 0000 0D00 0000 70 ....................p
21 0600 0085 0020 0000 0000 0019 0000 0048 0000 005F 5F ..... .........H...__
42 5041 4745 5A45 524F 0000 0000 0000 0000 0000 0000 00 PAGEZERO.............
... (673 very similar lines omitted) ...
14196 7075 7473 005F 7374 6174 2449 4E4F 4445 3634 005F 73 puts._stat$INODE64._s
14217 7472 6572 726F 7200 6479 6C64 5F73 7475 625F 6269 6E trerror.dyld_stub_bin
14238 6465 7200 0000 der...

Resources