A simple readline in a Linux CLI or TUI

Is there a simple way to write, in any programming language, a command that reads, in the terminal, an input line, allowing the user only to delete the last char he typed, and that will work without any problem of any kind, in all Linux “flavors”?

For example, if someone had to write this function in C, how did he achieve that?

This command/function has to work on any Linux flavour, on any kind of terminal emulator, in any country of the world…

Is that possible?

Actually I’m trying to do this but in Go. But I don’t think there’s a difference if I do this in Go or in C or in any other language, since, I guess, the problem is on the terminal, stty, config side…

I’m writing a TUI where - of course - I need to read the user’s input. I read it only one “key” at the time. With “key”, I mean: a single keyboard’s key or a combination of them: a letter, a digit, a function key, a keypad’s key; the Enter, Esc, or Tab key; the Ctrl+something combination of keys, and so on. (I don’t even know the right term I should use to define that “input key”.)

I understand the “escape sequences” quite well. I can easily read all keyboard and mouse inputs. All of them start with the “escape” ASCII character. I know how to parse those sequences and I can manage all kinds of user’s input; I can even follow the mouse cursor while it’s moving on the screen. I know the position of the cursor, the row and the column number, I know on which cell the user clicked and which mouse button he clicked. I can even know when and where he’s using the mouse wheel…

What I don’t understand is the “stty” thing and all the issues that arise from using different kinds of terminal’s emulators. I guess all problems come from that.

Since I have no experience in this kind of matters, I’ve googled around, and I’ve found solutions that work for me. But it’s very likely that, for another person, in another part of the world, and which uses another Linux’s flavor and another kind of terminal emulator, this solutions won’t work. I use the gnome terminal on Ubuntu 24.

In my program, the user usually has to input one “key” at the time, and I use that single key to do something. But other times the user has to input a whole line.

I wrote a function like this one below, to read a whole line from the user. Of course I have to read that line one “key” at the time:

var char rune
var line, s string
for {
	fmt.Scanf("%c", &char)
	s = string(char)
	switch s {
	case BACKSPACE:
		// delete the last "input key" from line
		// ...
	case ESC:
		// dump this new "input "key": it's an escape sequence
		// ...
	case ENTER:
		// return the line
		return line
	default:
		// add the new "char"
		line += s
	}
}

→ Add, to the line, any key the user is typing, discarding the escape sequences (arrows, keypads, mouse buttons, and so on). When he types the backspace, delete the last key you had read. When he types Enter, you are finished.

Maybe this function is not very well written, but, in my computer, it works fine… only if I put this line before:

exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()

which is the same, I guess, as if I were writing this in bash:

stty -F /dev/tty cbreak min 1

I tried studying what that exactly means, but I’m still away from a deep understanding… Unfortunately, I don’t feel familiar with these “tty”, “stty”, “config” things. I guess they are simple, but I just don’t get it at the moment.

Anyway, with that “stty” command, I can read one “input key” at the time, and that’s it.

Now, other people tried this command of mine and it didn’t work. There were issues when reading from the command line.

The question is:

→ How can I achieve a simple thing like this: reading an input line from the user, being certain that it will work on any Linux distro and on any terminal emulator?

1 Like

I was lazy to write an example by myself and searched for “linux C function to read string from terminal” The search returned the following results:

fgets() reads a line from the standard input (stdin) up to a predefined limit, protecting your program from buffer overflows

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

int main() {
    char buffer[256];

    printf("Enter a string: ");
    // Read from stdin safely
    if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
        
        // Optional: Strip the trailing newline character (\n)
        buffer[strcspn(buffer, "\n")] = '\0';

        printf("You entered: %s\n", buffer);
    }
    return 0;
}

For building command-line utilities (like a custom shell or a CLI tool) where you want terminal-like amenities—such as command history, arrow-key navigation, and live text editing—use the GNU Readline library

#include <stdio.h>
#include <stdlib.h>
#include <readline/readline.h>
#include <readline/history.h>

int main() {
    // Displays the prompt and allocates a string
    char *input = readline("CLI-Prompt> ");

    if (input != NULL) {
        if (input[0] != '\0') {
            add_history(input); // Add to up/down arrow history
            printf("Executed command: %s\n", input);
        }
        free(input); // Free allocated memory
    }
    return 0;
}

Hopefully, that may help.

3 Likes

My bad!

Just substituted ‘C’ with ‘golang’ in search and got

Good luck!

2 Likes

Thank you. But this is not what I was asking for. The problem is that if for example the user presses the backspace, it won’t work: instead of erasing the last char, it will add some “^b” (ctrl b) to the line. And that is the reason why I must read the input char by char and parse it.

1 Like

I didnt explain myself well. I know there are packages to achieve that in Go. But they are very complex and I just cant believe you need all that just to read a line! There must be a simpler way.

1 Like

What about

1 Like

As you may see there are some 200 functions in this package… Just to read an input line? I dont like that.

1 Like

Yeah, that’s a LOT of overhead. It’s probably an extremely rich and powerful library of functions, but it does “seem” to be “over the top” when a few small functions and some escape clauses for expected and unexpected errors are included, but 200 of these seems excessive. @hydn and others, do you have experience to suggest an efficient, sufficient, but hopefully much leaner approach, as @goatherd is seeking? I have no idea myself and the C code is the most familiar looking code in this space that I presently understand.

2 Likes

Surely, the package covers a lot possible use cases. That’s the essence of a popular package. A programmer is able to

  • use the package in question as is
  • reimplement the required function by himself from scratch
  • get the package source and simplify it to a desired extent

You choose :man_shrugging:

3 Likes

Great recommendation. IF you want to learn something, assuming the source code is available and it ought to be if it’s truly “free” software, dig into it and hack away; keep a copy of the complete effort, and hack down another copy until it suits your own needs. You’ll learn something and you’ll have a custom piece of software that does precisely what you’re looking for!

2 Likes