简体   繁体   中英

Why can this code read from stdin when it should be empty?

The code comes from the Kilo project:

/*
 * Use write() and terminal escape (ESC) sequence
 * to query cursor position.
 */

#include <stdio.h>
#include <unistd.h>

#define ESC 27

int main(int argc, char const *argv[]) {
    char buf[32];
    unsigned int i = 0;
    int rows, cols;

    write(STDOUT_FILENO, "\033[6n", 4);

    while (i < sizeof(buf)-1) {
        if (read(STDIN_FILENO,buf+i,1) != 1) break;
        if (buf[i] == 'R') break;
        i++;
    }
    buf[i] = '\0';

    /* Parse it. */
    if (buf[0] != ESC || buf[1] != '[') return -1;
    if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
    printf("%d %d\n", rows, cols);

    return 0;
}

The result in OS X terminal.app is:

➜  /Users/name/Desktop/demo ./write
^[[44;1R
44 1
➜  /Users/name/Desktop/demo 
➜  /Users/name/Desktop/demo 

The result has 5 lines, but I can't understand the actions:

  1. Why does it output those characters in line 2? Because the write() function has written some characters to STDOUT ? If yes, how can I suppress this output?

  2. Why can the read() function read something from STDIN ? I just think STDIN should be empty now because I have not pressed any key.

  3. After line 2, it will block. Why must I press ENTER ?

  4. Why is line 4 empty? Because of the ENTER that I just pressed?

Running in OS X 10.11.6 Terminal.app

The escape sequence \\e[6n is used to query the terminal for the cursor position. The terminal answers by feeding characters as if they had been typed by the user. The answer format is \\e[y;xR where y is the row number and x the column number, both 1 based. Here \\e[44;1R which is echoed by the terminal in line mode, like any other user input. Note that the escape byte is echoed as ^[ . You can edit this output with the backspace key. Try modifying the column number by changing it to ^[[44;11R and see the effect when you press ENTER.

The program tries to read this input but since the terminal is in line mode, you need to type the ENTER key for a full line of input to become available to the system.

Note that the program reads this input from the system byte by byte with individual read() system calls. It stops reading when it gets an R , therefore the linefeed stays in the system buffers.

The program's sscanf() parses the \\e[44;1 , prints 44 1 (row 44, column 1) and exits.

The shell echoes a command prompt, reads the pending linefeed and echoes another command prompt.

I'm not sure how to suppress the line2 output. I doubt redirecting stdout would make a difference.

Setting the terminal in raw mode with the stty() system call before the write() should prevent this output as well as the need for the ENTER key. If you change the terminal mode, aka the line discipline , remember to save the current settings and restore them before exiting the program.

If you want to not need the return, you need to take the terminal out of canonical mode. If you want it to not echo the response string, you need to disable echoing. You do both of those with tcsetattr . Add to your program:

At the top:

#include <errno.h>
#include <termios.h>

At the start of main :

struct termios old, modified;

if (tcgetattr(STDIN_FILENO, &old) < 0) {
    if (errno == ENOTTY)
        fprintf(stderr, "input is not a terminal -- can't query position\n");
    else
        perror("tcgetattr");
    exit(1); }
modified = old;
modified.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &modified);

At the end of main (before the return):

tcsetattr(STDIN_FILENO, TCSANOW, &old);

This disables canonical mode and echoing at the start of the program, and restores the previous settings afterwards.

Also, you actually want to write the query sequence to STDIN_FILENO rather than STDOUT_FILENO . That way it will continue to work properly even if you redirect the output somewhere...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM