2

Situation: I have a C program that takes a string argument and does stuff to it (details are not relevant.) I want to pass it a string that contains a control character, such as EOF, CR, or LF, and cannot switch my keyboard to raw input mode in the terminal. My question is: Does C have any functionality that will allow me to indicate or "type" the character in some way? (For instance, you can escape characters with a slash or indicate their hex codes when making strings in some languages. I am wondering if anything similar exists with regard to passing arguments to a C program, from within the terminal - so I am asking about command line arguments specifically.)

mage
  • 626
  • 2
  • 8
  • 18
  • 3
    There are typically hotkeys for this (Ctrl+D = EOF, etc...) check your terminal application for the appropriate ones. Or if you mean passing the actual character in a C-string you can just type it into the c-string using it's literal ("\r", "\n", etc...). – Jesus Ramos May 01 '13 at 20:56
  • I mean a command line argument. And the problem with the hotkeys is that, unless I'm in raw mode, they don't type the character. For instance, Ctrl+D actually exits whatever program I'm in. – mage May 01 '13 at 21:08
  • Those are probably not the best ways to handle things in your program then if you require many of these control characters. – Jesus Ramos May 01 '13 at 21:10
  • Yes... I would still like an answer to my question though. :) – mage May 01 '13 at 21:11
  • Unfortunately that's how some control characters work since they are non-standard ("\n" vs "\r\n") – Jesus Ramos May 01 '13 at 21:13
  • some shells and/or terminals will let you enter raw characters by preceding the character with a quote character; for example using bash in linux you can usually enter a literal Ctrl-D by typing Ctrl-V Ctrl-D. YMMV, of course. – evil otto May 02 '13 at 00:01
  • I actually answered my own question, you can use perl or python to generate the string in question very easily... should I post as an answer? – mage May 05 '13 at 00:24

3 Answers3

2

You probably want to learn how to pass binary data as argument in bash. See this question.

Here is a short demo in C. The program prints out every argument passed to it, character by character, in hex.

/* compile with cc -o binarg binargs.c */

#include <stdio.h>

int main(int argc, char *argv[])
{
    int i;
    char *ip;
    printf("\n");
    for(i=0; i<argc; i++)
    {
        printf("argv[%d]=%s\n",i,argv[i]);
        for(ip=(char*)argv[i]; *ip!=0; ip++)
        {
            printf("0x%02X <-\t`%c'\n",*ip,*ip);
        }
        printf("\n");
    }
    return 0;
}

let's pass binary arguments to it, as instructed in the post mentioned above.

./binargs ABC $'\x41\x42\x43' $'\t\n'

results:

argv[0]=./binargs
0x2E <- `.'
0x2F <- `/'
0x62 <- `b'
0x69 <- `i'
0x6E <- `n'
0x61 <- `a'
0x72 <- `r'
0x67 <- `g'
0x73 <- `s'

argv[1]=ABC
0x41 <- `A'
0x42 <- `B'
0x43 <- `C'

argv[2]=ABC
0x41 <- `A'
0x42 <- `B'
0x43 <- `C'

argv[3]=

0x09 <- `       '
0x0A <- `
'

argv[0] is the name of our program binargs itself

argv[1] is a regular string "ABC"

argv[2] is the same as argv[1], but in hex

argv[3] is a sequense of two control characters: HT LF

Note that it uses the traditional Unix way to quote each character, so we can see the boundary of non-printable characters when they are printed:

./binargs $'\a\b\v\f'  

( let's skip the argv[0] part )

argv[1]=


0x07 <- `'
0x08 <- '
0x0B <- `
         '
0x0C <- `
         '

or we can pipe it to cat -v

./binargs $'\a\b\v\f' | cat -v

which makes the result more readable:

argv[1]=^G^H^K^L
0x07 <- `^G'
0x08 <- `^H'
0x0B <- `^K'
0x0C <- `^L'
Community
  • 1
  • 1
btwiuse
  • 2,585
  • 1
  • 23
  • 31
0

In this situation you need to be very conscious of where the functionality is. Processing may or may not be done by the command line interpreter that calls your program. You may get different results from the same C code depending on whether your are using a DOS command line, or Linux or Unix, etc.

Logicrat
  • 4,438
  • 16
  • 22
  • Which shell is being used can also impact it, along with terminal settings. There is no "portable" answer to this one, I suspect. – Randy Howard May 01 '13 at 21:17
0

If you're satisfied with being able to send C-string-style escape sequences through a conversion function, the following may work for you, as long as you don't use quotes within the middle of the input. If you need quotes in the middle of the input, you'll have to figure out what escape sequence your shell uses to pass quotes through the command line args.

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

// Unescapes 'str' in place, collapsing it back on itself, and
// returns the resulting length of the collapsed buffer.  Handles
// mid-buffer nul characters (0x00).  You can easily add your own
// special escape sequences if you wish.  Just be sure that no escape
// sequence translates into more characters than it takes to encode
// the escape sequence itself in the original string.
int unescape(char* str)
{
    char *out, *in;
    int len=0;
    in = out = str; // both start at the same place
    while(*in)
    {
        char c = *in++;
        if (c != '\\')
            *out++ = c; // regular, unescaped character
        else
        {                   // escaped character; process it...
            c = *in++;
            if      (c == '0') *out++ = '\0';
            else if (c == 'a') *out++ = '\a';
            else if (c == 'b') *out++ = '\b';
            else if (c == 'f') *out++ = '\f';
            else if (c == 'n') *out++ = '\n';
            else if (c == 'r') *out++ = '\r';
            else if (c == 't') *out++ = '\t';
            else if (c == 'v') *out++ = '\v';
            else if (c == 'x'  // arbitrary hexadecimal value
                    && isxdigit(in[0]) && isxdigit(in[1]))
            {
                char x[3];
                x[0] = *in++;
                x[1] = *in++;
                x[3] = '\0';
                *out++ = strtol(x, NULL, 16);
            }
            else if (c>='0' && c<='3' // arbitrary octal value
                    && in[0]>='0' && in[0]<='7'
                    && in[1]>='0' && in[1]<='7')
            {
                *out++ = (c-'0')*64 + (in[0]-'0')*8 + (in[1]-'0');
                in += 2;
            }
            else // any other char following '\' is just itself.
                *out++ = *in++;
        }
        ++len; // each time through the loop adds one character
    }
    *out = '\0';
    return len;
}

void print_buf(const char* buf, int len)
{
    int col;
    unsigned char* cp = (unsigned char*)buf;
    for (col=0; len>0; --len, ++col)
        printf(" %02x%s", *cp++, ((col&16==15) ? "\n" : ""));
}

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

    if (argc<2)
    {
        fprintf(stderr, "First arg must be a string, "
                "and it probably ought to be quoted.\n");
        exit(1);
    }

    printf("\nInput string: \"%s\"\n", argv[1]);
    print_buf(argv[1], strlen(argv[1]));

    str = malloc(strlen(argv[1]));
    strcpy(str, argv[1]);
    len = unescape(str);
    printf("\nunescape() produces the following:\n");
    print_buf(str, len);
    free(str);

    printf("\n");
}
phonetagger
  • 7,701
  • 3
  • 31
  • 55