我正在try 从stdin读取输入(Markdown),然后以一种对终端友好的方式输出,并使用彩色输出.为此,我使用termbg个 crate 确定航站楼的主题(亮/暗).

要读取通过管道传输到我的程序的内容,我使用以下命令:

fn read_from_stdin() -> String {
  let stdin = io::stdin();
  let stdin_lock = stdin.lock();
  let buffer = stdin_lock.lines().filter_map(|l| l.ok()).fold("".to_string(), |acc, s| { format!("{acc}\n{s}")});
  return buffer;
}

在我读完stdin之后,我try 找出终端使用的是亮主题还是暗主题,使用的是termbg箱:

let timeout = std::time::Duration::from_millis(100);
let theme = termbg::theme(timeout);
match theme {
  Ok(t) => {
    println!("Detected theme: {:?}", t);
  },
  _ => {
    println!("No theme detected!"); // happens with tmux
  }
}

The problem is, termbg is trying to determine the theme by using OSC queries that apparently require the use of stdin/stdout.
Determining the right theme only works if I replace the reading from stdin with a hardcoded string and if I won't pass anything in via pipe.
So, apparently, termbg needs a clean and empty stdin to work properly but I can't find a way to make sure it gets that.

How can I make sure that stdin is empty and in a state that termbg can work with before attempting to determine the right terminal theme?

完整的可测试代码:

use std::{io::{self, BufRead}}

fn main() => io::Result<()> {
  let buffer = read_from_stdin();
  
  let timeout = std::time::Duration::from_millis(100);
  let theme = termbg::theme(timeout);
  match theme { // Mostly outputs wrong theme or none at all
    Ok(t) => {
      println!("Detected theme: {:?}", t);
    },
    _ => {
      println!("No theme detected!"); // happens with tmux
    }
  }
  
  // Parsing and formatting the markdown read from stdin would follow here but isn't relevant
  
  return Ok(());
}

fn read_from_stdin() -> String {
  let stdin = io::stdin();
  let stdin_lock = stdin.lock();
  let buffer = stdin_lock.lines().filter_map(|l| l.ok()).fold("".to_string(), |acc, s| { format!("{acc}\n{s}")});
  return buffer;
}

推荐答案

恐怕您不能在启动时已经重定向标准输入(由此处的shell 重定向),同时try 使用标准输入与终端交互.

因为stdin的用法似乎是在termbg中硬编码的,所以我们必须让标准输入保持不变,并重定向到另一个文件描述符.

下面是一个读取由shell 打开的另一个文件描述符的示例.

当然,将文件名提供给程序本身会更容易,但我认为实际用例需要外部重定向.

use std::{
    io::{IsTerminal, Read},
    os::fd::FromRawFd,
};

fn main() {
    println!(
        "is stdin still a terminal? ~~> {}",
        std::io::stdin().is_terminal()
    );
    let fd = 3;
    let mut input = unsafe { std::fs::File::from_raw_fd(fd) };
    let mut content = String::new();
    if let Ok(n) = input.read_to_string(&mut content) {
        println!("from fd {}, {} bytes: {:?}", fd, n, content);
    }
}
/*
$ (exec 3< file.txt ; cargo run)
is stdin still a terminal? ~~> true
from fd 3, 10 bytes: "Hi there!\n"
*/

另一种解决方案是将标准输入复制到另一个文件描述符,即使用管道将标准输入复制到另一个文件描述符before. 这样,标准输入像往常一样重定向到管道,这样我们就可以读取内容,但这个新的文件描述符仍然引用原始终端. 一旦我们完成了标准输入(从管道)的读取,我们将终端重新分配给它的初始文件描述符(使用classic 的dup2()/close()序列,感谢libc crate),然后std::io::stdin()又是一个终端.也许termbg将从这一点开始工作.

use std::io::{IsTerminal, Read};

fn main() {
    println!(
        "is stdin initialy a terminal? ~~> {}",
        std::io::stdin().is_terminal()
    );
    let mut content = String::new();
    match std::io::stdin().read_to_string(&mut content) {
        Ok(n) => println!("from stdin, {} bytes: {:?}", n, content),
        Err(e) => println!("cannot read stdin: {:?}", e),
    }
    //~~~~ redirect fd 3 as stdin ~~~~
    let fd = 3;
    if unsafe { libc::dup2(fd, 0) } == -1 {
        println!("cannot redirect fd {} to stdin", fd);
        return;
    }
    if unsafe { libc::close(fd) } == -1 {
        println!("cannot close fd {}", fd);
        return;
    }
    //~~~~ stdin should be the terminal now ~~~~
    println!(
        "is stdin now a terminal? ~~> {}",
        std::io::stdin().is_terminal()
    );
}
/*
$ (exec 3<&0 ; echo "Hi there!" | cargo run )
is stdin initialy a terminal? ~~> false
from stdin, 10 bytes: "Hi there!\n"
is stdin now a terminal? ~~> true
*/

要在Perl脚本(而不是shell )中使用它,您可以try

#~~~~ duplicate stdin as fd 3 ~~~~
use POSIX ();
POSIX::dup2(0, 3);
#~~~~ open a pipe to the stdin of the specified command ~~~~
open(my $my_pipe, '|-', 'cargo', 'run');
#~~~~ write anything into this pipe ~~~~
print $my_pipe "Hi there!\n";

Rust相关问答推荐

如何定义使用拥有的字符串并返回拥有的Split的Rust函数?

我怎样才能从一个Rust 的日期中go 掉3年?

制作一片连续整数的惯用Rust 方法?

在不重写/专门化整个函数的情况下添加单个匹配手臂到特征的方法?

新创建的变量的绑定生存期

在UdpSocket上使用sendto时的隐式套接字绑定

是否可以使用Rust宏来构建元组的项?

什么时候和为什么S最好是按值或引用传递简单类型

在复制类型中使用std::ptr::WRITE_VILAR进行内部可变性的安全性(即没有UnSafeCell)

Rust ndarray:如何从索引中 Select 数组的行

为什么 Rust 创建的 f32 小于 f32::MIN_POSITIVE?

借来的价值生命周期 不够长,不确定为什么它仍然是借来的

在 Rust 中,为什么 10 个字符的字符串的 size_of_val() 返回 24 个字节?

提取指向特征函数的原始指针

为什么某些类型参数仅在特征边界中使用的代码在没有 PhantomData 的情况下进行编译?

为什么我的trait 对象类型不匹配?

borrow 匹配手臂内部的可变

rust 中不同类型的工厂函数

如何展平以下嵌套的 if let 和 if 语句?

如何在不设置精度的情况下打印浮点数时保持尾随零?