3

Hello sorry if Im not understandable, Im new in c programming and Im not the best English writer.

My question: I cant understand how can I use backspace while using the code and I glad if someone could explain me how it works.

    #include <stdio.h>
int main()
{
    char name[30], ch;
    int i = 0;
    printf("Enter name: ");
    while(ch != '\n')    // terminates if user hit enter
    {
        ch = getchar();
        name[i] = ch;
        i++;
    }
    name[i] = '\0';       // inserting null character at end
    printf("Name: %s", name);
    return 0;
}

When I run this program I can actually write my name and while I'm writing I can use backspace to delete characters and then continue writing, how is that possible? because from what I understand this code enters any char to name array right after I'm tapping. Thank you, Jonatan.

  • There are standard and external library functions that allow users to enter strings. Any reason why you don't use one of these? – Jongware Nov 11 '18 at 12:04
  • 1
    If you want a simple `backspace`, then ASCII character `8` is the backspace character. It's used twice. Call `putchar (8)` to back-over the character, then `putchar (' ')` to clear the char, then `putchar (8)` again to position the cursor before the deleted character. Simple, basic, effective. The 'key' to catch for the backspace character is ASCII `127` on most x-terms. (`del`) – David C. Rankin Nov 11 '18 at 12:08
  • @DavidC.Rankin Your meaning is, when I press the backspace button on my keybaord thats the process that happens. Or you just explaining me how to use backspace while using getchar();? – Jonathan Shimoni Nov 11 '18 at 12:18
  • @usr2564301 Which functions are they? and I hate scanf() because it have a lot of restrictions. – Jonathan Shimoni Nov 11 '18 at 12:21
  • Warning: your loop starts with an uninitialized value in `ch`. If this happens to be `\n`, the loop will never be executed and you will never be able to enter any character (backspace or otherwise). – Jongware Nov 11 '18 at 12:21
  • @YoniShimoni - that is how you make it happen You are looping taking chars from the user. If he enters `127` that is the backspace key, so then you `putchar (8)` to backup, `putchar (' ')` (space) to clear the char, and then `putchar (8)` again so the next char typed takes its place. – David C. Rankin Nov 11 '18 at 12:25
  • 1
    When you type 'ab', your program sees *absolutely nothing*. When you then hit , your program will get the 2 characters 'a' and a newline. The terminal is doing a lot of buffering for you. – William Pursell Nov 11 '18 at 12:35

3 Answers3

3

Yoni, you have two good answers, but for completeness, I'll provide the remaining information you had questions with in the comments.

First when taking any input your want to display and allow minimal user editing of, you will need to put your keyboard in non-cannonical mode so that each keypress is available to your program as the key is typed -- no need to wait for the user to press Enter. You handle this with tcgetattr (terminal get attributes) and tcsetattr (terminal set attributes). *non-cannonical mode is what windows getch() provides.

Essentially you set up your read-loop to read with getchar or fgetc (if you want to be able to read the values from stdin or a file.) You control the read loop with:

#define MAXPW 32    /* constant for max input length */

int main (int argc, char **argv) {

    int c,
        idx = 0;                        /* buf index */
    char pw[MAXPW] = "",                /* buf for passwd */
        mask = argc > 1 ? *argv[1] : 0; /* mask off by default */
    FILE *fp = stdin;
    ...
    /* read chars from fp, mask w/mask char */
    while ((idx + 1 < MAXPW && (c = fgetc (fp)) != '\n' && c != EOF) ||
            (idx == MAXPW - 1 && c == 127))
    {

Note carefully, you are reading a character while (1) (space_remains AND c isn't '\n' or EOF) OR (2) (space_remains AND backspace_key_pressed)

In that even you handle the two cases (1) was it a normal char - add it; or (2) was it a backspace character, then backup, overwrite char with space and backup again, e.g.

        if (c != 127) {                 /* not the backspace characters */
            if (' ' - 1 < mask && mask < 127)   /* if mask valid ASCII */
                fputc (mask, stdout);   /* output mask char */
            else
                fputc (c, stdout);      /* output normal char */
            pw[idx++] = c;              /* store char, adv index */
        }
        else if (idx > 0) {             /* handle backspace (del)   */
            fputc (0x8, stdout);        /* backup */
            fputc (' ', stdout);        /* overwrite with space */
            fputc (0x8, stdout);        /* backup again */
            pw[--idx] = 0;              /* nul-termiante after current */
        }

note: if your mask character is a printable character, then the mask character is output, e.g.

enter passwd: ********

If the mast is not printable (default is nul), then the text is output. You can set the mask character as the 1st argument to the program (in quotes), e.g.

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

#include <sys/time.h>
#include <termios.h>
#include <errno.h>      /* for errno */
#include <unistd.h>     /* for EINTR */

#define MAXPW 32    /* constant for max input length */

int main (int argc, char **argv) {

    int c,
        idx = 0;                        /* buf index */
    char pw[MAXPW] = "",                /* buf for passwd */
        mask = argc > 1 ? *argv[1] : 0; /* mask off by default */
    FILE *fp = stdin;

    struct termios old_kbd_mode;    /* orig keyboard settings   */
    struct termios new_kbd_mode;

    if (tcgetattr (0, &old_kbd_mode)) { /* save orig settings   */
        fprintf (stderr, "%s() error: tcgetattr failed.\n", __func__);
        return -1;
    }   /* copy old to new */
    memcpy (&new_kbd_mode, &old_kbd_mode, sizeof(struct termios));

    /* put keyboard in non-canonical/no echo mode */
    new_kbd_mode.c_lflag &= ~(ICANON | ECHO);  /* new kbd flags */
    new_kbd_mode.c_cc[VTIME] = 0;
    new_kbd_mode.c_cc[VMIN] = 1;
    if (tcsetattr (0, TCSANOW, &new_kbd_mode)) {
        fputs ("error: tcsetattr failed.\n", stderr);
        return -1;
    }

    fputs ("enter passwd : ", stdout);  /* set passwd prompt */

    /* read chars from fp, mask w/mask char */
    while ((idx + 1 < MAXPW && (c = fgetc (fp)) != '\n' && c != EOF) ||
            (idx == MAXPW - 1 && c == 127))
    {
        if (c != 127) {                 /* not the backspace characters */
            if (' ' - 1 < mask && mask < 127)   /* if mask valid ASCII */
                fputc (mask, stdout);   /* output mask char */
            else
                fputc (c, stdout);      /* output normal char */
            pw[idx++] = c;              /* store char, adv index */
        }
        else if (idx > 0) {             /* handle backspace (del)   */
            fputc (0x8, stdout);        /* backup */
            fputc (' ', stdout);        /* overwrite with space */
            fputc (0x8, stdout);        /* backup again */
            pw[--idx] = 0;              /* nul-termiante after current */
        }
    }
    pw[idx] = 0; /* null-terminate final string */

    /* restore original keyboard mode */
    if (tcsetattr (0, TCSANOW, &old_kbd_mode)) {
        fputs ("error: tcsetattr failed.\n", stderr);
        return -1;
    }

    printf ("\nstored passwd: %s\n", pw);
}

An Editable Input

Running the program without a mask, say the user enters:

$ ./bin/backspace
enter passwd : my_password_is_bad

(the user thinks about and says "oh, that's not good" and can now hit the backspace key 3 times which leaver her looking at:

$ ./bin/backspace
enter passwd : my_password_is_

Now she completes her input:

$ ./bin/backspace
enter passwd : my_password_is_good
stored passwd: my_password_is_good

The operations work exactly the same with the mask character displayed. With all mask characters displayed and the user forgot what she had entered, she could simply backspace over all characters show (and keep smashing the backspace key if she feels like it, and then proceed again to enter a correct password (name, whatever). Example with a mask char of '*', e.g.

$ ./bin/backspace '*'
enter passwd : *******************
stored passwd: my_password_is_good

It's a handy way to provide minimal edit capabilities to the user in some circumstances. If you don't need to mask the user input, then you can completely do away with changing the keyboard mode.

Look things over and let me know if you have questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
2

Userspace C's stdio won't talk directly with the hardware. It'll talk with the OS. And the OS will usually do quite a bit of preprocessing on the keystrokes it receives before it sends them to an application. On a UNIX-like OS much of the preprocessing will be done by your terminal driver, which can be set to reset to raw mode in which case you will actually receive the backspace too. Playing with the terminal driver is not standardized by the C standard, though.

On Linux, I can do:

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

int main()
{
    char name[30], ch;
    int i = 0;
    printf("Enter name: ");
    system("stty raw");
    while(ch != '\n' && i < sizeof(name))    // terminates if user hit enter
    {
        ch = getchar();
        name[i] = ch;
        i++;
    }
    name[i] = '\0';       // inserting null character at end
    printf("Name: %s", name);
    system("stty sane"); //set some sane settings to the terminal
    return 0;
}

and then I get the raw characters (I need to type shift+Enter to send the \n).

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
1

I didn't test the code, but the main idea is something like this:

while(ch != '\n')    // terminates if user hit enter
{
    ch = getchar();
    // if this is a backspace character, 
    // lower the index and delete the last char
    if(ch == 0x08){
       name[--i] = 0x00;
    }else{
       // other chars will increment the index and fill the current char buffer
       name[i++] = ch;
    }
}

Late Edit:

Sorry I understood the question wrong I guess. The correct answer would be this:

Suppose you entered this: 1235[0x08]4 into the terminal.

Your char array would be:

[0x31, 0x02, 0x33, 0x35, 0x08, 0x34] 

And when you print it, it'll execute like this order and it'll print char by char. Likewise, 5 would be printed and backspaced so fast you wouldn't notice.

And here's another question that may give you some other idea that how backspace works in some environments:

The "backspace" escape character '\b': unexpected behavior?

Taha Paksu
  • 15,371
  • 2
  • 44
  • 78
  • Oh I get it so Im actually entering a backspace to my array? Wow you really helped me, and before you edit you actually gave me a why to do it in the right why. Thank you very much. – Jonathan Shimoni Nov 11 '18 at 12:26