应该在 ioctl 中使用哪个文件描述符来了解终端屏幕大小?

Which file descriptor should be used in ioctl to know terminal screen size?

即使应用程序 运行 在子 shell 中,我也想可靠地知道终端大小。 似乎唯一可行的方法是使用 stdin fd,但我想知道为什么要链接到证明它的文档。 有很多问题,比如为什么要使用 stdout(或 stdin 就此而言)fd 来了解终端大小?

fn main() {
    let mut size = winsize {
        ws_row: 0,
        ws_col: 0,
        ws_xpixel: 0,
        ws_ypixel: 0,
    };
    let fd = std::fs::OpenOptions::new()
        .read(true)
        .write(true)
        .open("/dev/tty")
        .map(|file| file.as_raw_fd())
        .unwrap()
        ;
    unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) };
    println!("/dev/tty cols: {}, lines: {}", size.ws_row, size.ws_col);
    unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size) };
    println!("stdout cols: {}, lines: {}", size.ws_row, size.ws_col);
    unsafe { ioctl(STDERR_FILENO, TIOCGWINSZ.into(), &mut size) };
    println!("stderr cols: {}, lines: {}", size.ws_row, size.ws_col);
    unsafe { ioctl(STDIN_FILENO, TIOCGWINSZ.into(), &mut size) };
    println!("stdin cols: {}, lines: {}", size.ws_row, size.ws_col);
}

我运行echo (cargo run 2>&1)在鱼shell中得到了这个:

   Compiling test_term_size v0.1.0 (/home/m/code/test_term_size) warning: unused import: `File`  --> src/main.rs:2:15   | 2 | use std::fs::{File};   |               ^^^^   |   = note: `#[warn(unused_imports)]` on by default  warning: unused imports: `O_RDWR`, `open`  --> src/main.rs:4:5   | 4 |     O_RDWR,   |     ^^^^^^ 5 |     open, ioctl, winsize, STDIN_FILENO,   |     ^^^^      Finished dev [unoptimized + debuginfo] target(s) in 0.21s      Running `target/debug/test_term_size` /dev/tty cols: 0, lines: 0 stdout cols: 0, lines: 0 stderr cols: 0, lines: 0 stdin cols: 52, lines: 106
strace -e trace=ioctl,open target/debug/test_term_size
ioctl(3, TIOCGWINSZ, 0x7ffdb9805578)    = -1 EBADF (Bad file descriptor)
/dev/tty cols: 0, lines: 0
ioctl(1, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0
stdout cols: 52, lines: 213
ioctl(2, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0
stderr cols: 52, lines: 213
ioctl(0, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0
stdin cols: 52, lines: 213
+++ exited with 0 +++
strace -e trace=ioctl,open cargo run
ioctl(2, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(2, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(2, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(2, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(2, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0
ioctl(2, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0
ioctl(2, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/test_term_size`
ioctl(3, TIOCGWINSZ, 0x7ffea819b008)    = -1 EBADF (Bad file descriptor)
/dev/tty cols: 0, lines: 0
ioctl(1, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0
stdout cols: 52, lines: 213
ioctl(2, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0
stderr cols: 52, lines: 213
ioctl(0, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0
stdin cols: 52, lines: 213
+++ exited with 0 +++
echo (strace -e trace=ioctl,open cargo run 2>&1)
ioctl(2, TCGETS, 0x7ffdae149cb0)        = -1 ENOTTY (Inappropriate ioctl for device) ioctl(2, TCGETS, 0x7ffdae149a30)        = -1 ENOTTY (Inappropriate ioctl for device) ioctl(2, TCGETS, 0x7ffdae149a30)        = -1 ENOTTY (Inappropriate ioctl for device) ioctl(2, TCGETS, 0x7ffdae149540)        = -1 ENOTTY (Inappropriate ioctl for device) warning: unused import: `File`  --> src/main.rs:2:15   | 2 | use std::fs::{File};   |               ^^^^   |   = note: `#[warn(unused_imports)]` on by default  warning: unused imports: `O_RDWR`, `open`  --> src/main.rs:4:5   | 4 |     O_RDWR,   |     ^^^^^^ 5 |     open, ioctl, winsize, STDIN_FILENO,   |     ^^^^      Finished dev [unoptimized + debuginfo] target(s) in 0.02s      Running `target/debug/test_term_size` ioctl(3, TIOCGWINSZ, 0x7ffd8c3ae728)    = -1 EBADF (Bad file descriptor) /dev/tty cols: 0, lines: 0 ioctl(1, TIOCGWINSZ, 0x7ffd8c3ae728)    = -1 ENOTTY (Inappropriate ioctl for device) stdout cols: 0, lines: 0 ioctl(2, TIOCGWINSZ, 0x7ffd8c3ae728)    = -1 ENOTTY (Inappropriate ioctl for device) stderr cols: 0, lines: 0 ioctl(0, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0 stdin cols: 52, lines: 213 +++ exited with 0 +++
echo (strace -e trace=ioctl,open target/debug/test_term_size 2>&1)
ioctl(3, TIOCGWINSZ, 0x7fffdc82e938)    = -1 EBADF (Bad file descriptor) /dev/tty cols: 0, lines: 0 ioctl(1, TIOCGWINSZ, 0x7fffdc82e938)    = -1 ENOTTY (Inappropriate ioctl for device) stdout cols: 0, lines: 0 ioctl(2, TIOCGWINSZ, 0x7fffdc82e938)    = -1 ENOTTY (Inappropriate ioctl for device) stderr cols: 0, lines: 0 ioctl(0, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0 stdin cols: 52, lines: 213 +++ exited with 0 +++
echo (strace -e trace=ioctl,open target/debug/test_term_size 2>&1)
ioctl(3, TIOCGWINSZ, 0x7fffdc82e938)    = -1 EBADF (Bad file descriptor) /dev/tty cols: 0, lines: 0 ioctl(1, TIOCGWINSZ, 0x7fffdc82e938)    = -1 ENOTTY (Inappropriate ioctl for device) stdout cols: 0, lines: 0 ioctl(2, TIOCGWINSZ, 0x7fffdc82e938)    = -1 ENOTTY (Inappropriate ioctl for device) stderr cols: 0, lines: 0 ioctl(0, TIOCGWINSZ, {ws_row=52, ws_col=213, ws_xpixel=3834, ws_ypixel=2028}) = 0 stdin cols: 52, lines: 213 +++ exited with 0 +++

TTY 信息的正确来源始终是 /dev/tty。不要使用 stdin 或任何其他奇怪的技巧。这就是它看起来对您不起作用的原因:

let fd = std::fs::OpenOptions::new()
    .read(true)
    .write(true)
    .open("/dev/tty")
    .map(|file| file.as_raw_fd())
    .unwrap()
    ;

您正在打开 /dev/tty 并获取它的 FD,但是文件随后被丢弃,因此在您可以使用它之前被关闭,所以它上面的 ioctl 失败并显示 EBADF "Bad file descriptor" .要解决此问题,请在使用完 FD 之前保持文件处于活动状态。