Getting a hexadecimal number into a program via the command line
Asked Answered
A

6

14

I can do this:

int main(int argc, char** argv) {
  unsigned char cTest = 0xff;
  return 0;
}

But what's the right way to get a hexadecimal number into the program via the command line?

unsigned char cTest = argv[1];

doesn't do the trick. That produces a initialization makes integer from pointer without a cast warning.

Assail answered 29/1, 2010 at 15:15 Comment(4)
Command line arguments are always strings. Some platforms may make them Unicode, in which case a simple strtol will not work.Banquet
@dirkgently: in which case mbstol or whatever the multibyte or wide char (or TCHAR) variants would cover that? (I also doubt this is a key concern given the nature of the questionLandgrabber
@dirkgently: in that case, is the second parameter of main() not of type char **? That can't be standards-compliant.Hystero
@Alok: They could still be multi-byte in which case mbstoul would be best, but that's far too fancy in the context of the question methinksLandgrabber
J
14

As the type of main indicates, arguments from the command line are strings and will require conversion to different representations.

Converting a single hexadecimal command-line argument to decimal looks like

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

int main(int argc, char *argv[])
{
  printf("%ld\n", strtol(argv[1], NULL, 16));

  return 0;
}

Example usage:

$ ./hex ff
255

Using strtol and changing the final argument from 16 to 0 as in

printf("%ld\n", strtol(argv[1], NULL, 0));

makes the program accept decimal, hexadecimal (indicated by leading 0x, and octal (indicated by leading 0) values:

$ ./num 0x70
112
$ ./num 070
56
$ ./num 70
70

Using the bash command shell, take advantage of ANSI-C Quoting to have the shell perform the conversion, and then your program just prints the values from the command line.

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

int main(int argc, char *argv[])
{
  int i;
  for (i = 1; i < argc; i++) {
    unsigned char value = argv[i][0];
    if (strlen(argv[i]) > 1)
      fprintf(stderr, "%s: '%s' is longer than one byte\n", argv[0], argv[i]);

    printf(i + 1 < argc ? "%u " : "%u\n", value);
  }

  return 0;
}

Bash supports many formats of the form $'...', but $'\xHH' appears to be the closest match to your question. For example:

$ ./print-byte $'\xFF' $'\x20' $'\x32'
255 32 50

Maybe you pack the values from the command line into a string and print it.

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

int main(int argc, char *argv[])
{
  int i;

  if (argc > 1) {
    char *s = malloc(argc);
    if (!s) {
      fprintf(stderr, "%s: malloc: %s\n", argv[0], strerror(errno));
      return 1;
    }

    for (i = 1; i < argc; i++)
      s[i - 1] = strtol(argv[i], NULL, 16) & 0xff;

    s[argc - 1] = '\0';
    printf("%s\n", s);
    free(s);
  }

  return 0;
}

In action:

$ ./pack-string 48 65 6c 6c 6f 21
Hello!

All of the above is reinventing wheels that bash and the operating system already provide for you.

$ echo $'\x48\x65\x6c\x6c\x6f\x21'
Hello!

The echo program prints its command-line arguments on the standard output, which you can think of as a for loop over the arguments and a printf for each.

If you have another program that performs the decoding for you, use Command Substitution that replaces a command surrounded by backticks or $() with its output. See examples below, which again use echo as a placeholder.

$ echo $(perl -e 'print "\x50\x65\x72\x6c"')
Perl
$ echo `python -c 'print "\x50\x79\x74\x68\x6f\x6e"'`
Python
Joplin answered 29/1, 2010 at 15:20 Comment(0)
S
18

I think some people arriving here might just be looking for:

$ ./prog `python -c 'print "\x41\x42\x43"'`
$ ./prog `perl -e 'print "\x41\x42\x43"'`
$ ./prog `ruby -e 'print "\x41\x42\x43"'`
Soembawa answered 16/7, 2011 at 16:33 Comment(0)
J
14

As the type of main indicates, arguments from the command line are strings and will require conversion to different representations.

Converting a single hexadecimal command-line argument to decimal looks like

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

int main(int argc, char *argv[])
{
  printf("%ld\n", strtol(argv[1], NULL, 16));

  return 0;
}

Example usage:

$ ./hex ff
255

Using strtol and changing the final argument from 16 to 0 as in

printf("%ld\n", strtol(argv[1], NULL, 0));

makes the program accept decimal, hexadecimal (indicated by leading 0x, and octal (indicated by leading 0) values:

$ ./num 0x70
112
$ ./num 070
56
$ ./num 70
70

Using the bash command shell, take advantage of ANSI-C Quoting to have the shell perform the conversion, and then your program just prints the values from the command line.

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

int main(int argc, char *argv[])
{
  int i;
  for (i = 1; i < argc; i++) {
    unsigned char value = argv[i][0];
    if (strlen(argv[i]) > 1)
      fprintf(stderr, "%s: '%s' is longer than one byte\n", argv[0], argv[i]);

    printf(i + 1 < argc ? "%u " : "%u\n", value);
  }

  return 0;
}

Bash supports many formats of the form $'...', but $'\xHH' appears to be the closest match to your question. For example:

$ ./print-byte $'\xFF' $'\x20' $'\x32'
255 32 50

Maybe you pack the values from the command line into a string and print it.

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

int main(int argc, char *argv[])
{
  int i;

  if (argc > 1) {
    char *s = malloc(argc);
    if (!s) {
      fprintf(stderr, "%s: malloc: %s\n", argv[0], strerror(errno));
      return 1;
    }

    for (i = 1; i < argc; i++)
      s[i - 1] = strtol(argv[i], NULL, 16) & 0xff;

    s[argc - 1] = '\0';
    printf("%s\n", s);
    free(s);
  }

  return 0;
}

In action:

$ ./pack-string 48 65 6c 6c 6f 21
Hello!

All of the above is reinventing wheels that bash and the operating system already provide for you.

$ echo $'\x48\x65\x6c\x6c\x6f\x21'
Hello!

The echo program prints its command-line arguments on the standard output, which you can think of as a for loop over the arguments and a printf for each.

If you have another program that performs the decoding for you, use Command Substitution that replaces a command surrounded by backticks or $() with its output. See examples below, which again use echo as a placeholder.

$ echo $(perl -e 'print "\x50\x65\x72\x6c"')
Perl
$ echo `python -c 'print "\x50\x79\x74\x68\x6f\x6e"'`
Python
Joplin answered 29/1, 2010 at 15:20 Comment(0)
L
7

You could use strtoul which would walk through the characters in the string and convert them, taking into account the radix (16 in this context) that you pass in:-

char *terminatedAt;
if (argc != 2)
    return 1;

unsigned long value = strtoul( argv[1], &terminatedAt, 16);

if (*terminatedAt != '\0')
    return 2;

if (value > UCHAR_MAX)
    return 3;

unsigned char byte = (unsigned char)value;
printf( "value entered was: %d", byte);

As covered in the other examples, there are shorter ways, but none of them allow you to cleanly error check the processing (what happens if someone passes FFF and you've only got an unsiged char to put it into?

e.g. with sscanf:

int val;
sscanf(argv[1], &val)
printf("%d\n", val); 
Landgrabber answered 29/1, 2010 at 15:19 Comment(4)
UCHAR_MAX may be greater than LONG_MAX, so you should use strtoul(), and value > 255 should be value > UCHAR_MAX. Finally, static_cast is C++; here you don't need a cast because of implicit conversion rules.Hystero
Ta, I had a cout in it in the intial version :P I considered using u initially but thought it would complicate matters too much given that the questioner is mainly looking to understand a concept that isnt immediately obvious to them.Landgrabber
I agree that sometimes one has to make things not-entirely-pedantically-correct for beginners, but in this case I think the pedantry isn't too hard, even for a beginner. It will prevent many people from assuming that UCHAR_MAX == 255 or LONG_MAX > UCHAR_MAX, etc. Thanks for the edit and a good answer!Hystero
@Alok: Always happy to be pedantic, thanks! I think the cast is good here as it explains what's happening to a reader and will avoid warnings about the shortening conversion shoudl they be switched on. I'll leave out mbs concerns (and wchar_t concerns as the question ruled them out). Over and out!Landgrabber
H
4
unsigned char cTest = argv[1];

is wrong, because argv[1] is of type char *. If argv[1] contains something like "0xff" and you want to assign the integer value corresponding to that to an unsigned char, the easiest way would be probably to use strtoul() to first convert it to an unsigned long, and then check to see if the converted value is less than or equal to UCHAR_MAX. If yes, you can just assign to cTest.

strtoul()'s third parameter is a base, which can be 0 to denote C-style number parsing (octal and hexadecimal literals are allowed). If you only want to allow base 16, pass that as the third argument to strtoul(). If you want to allow any base (so you can parse 0xff, 0377, 255, etc.), use 0.

UCHAR_MAX is defined in <limits.h>.

Hystero answered 29/1, 2010 at 15:23 Comment(1)
+1 good explanation, taking in the reasoning behind most of the points I'm trying to show in the codeLandgrabber
D
1

The traditional way to do this kind of thing in C is with scanf(). It's exactly the inverse of printf(), reading the given format out of the file (or terminal) and into the variables you list, rather than writing them into it.

In your case, you'd use sscanf as you've already got it in a string rather than a stream.

Devote answered 29/1, 2010 at 15:24 Comment(0)
J
-3

atoi, atol, strtoi, strtol

all in stdlib.h

Jenks answered 29/1, 2010 at 15:21 Comment(1)
All pointed out 3 mins before (the see also of the linked man page) but not helping the questioner understand why its necessary or giving a sample. When a post isnt adding, it's good to delete it to keep it DRYLandgrabber

© 2022 - 2024 — McMap. All rights reserved.