简单的字数统计 rust 程序输出有效的标准输出,但在通过管道传输到具有特定内容的头程序时会出现恐慌

uto*_*man 7 unix compilation pipe broken-pipe rust

我在铁锈中有这样的痕迹:

\n
\nthread \'main\' panicked at \'failed printing to stdout: Broken pipe (os error 32)\', library/std/src/io/stdio.rs:993:9\nstack backtrace:\n   0:     0x559ffa959dc0 - std::backtrace_rs::backtrace::libunwind::trace::h72c2fb8038f1bbee\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/../../backtrace/src/backtrace/libunwind.rs:96\n   1:     0x559ffa959dc0 - std::backtrace_rs::backtrace::trace_unsynchronized::h1e3b084883f1e78c\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/../../backtrace/src/backtrace/mod.rs:66\n   2:     0x559ffa959dc0 - std::sys_common::backtrace::_print_fmt::h3bf6a7ebf7f0394a\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/sys_common/backtrace.rs:79\n   3:     0x559ffa959dc0 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h2e8cb764b7fe02e7\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/sys_common/backtrace.rs:58\n   4:     0x559ffa972f6c - core::fmt::write::h7a1184eaee6a8644\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/fmt/mod.rs:1080\n   5:     0x559ffa957b12 - std::io::Write::write_fmt::haeeb374d93a67eac\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/io/mod.rs:1516\n   6:     0x559ffa95beed - std::sys_common::backtrace::_print::h1d14a7f6ad632dc8\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/sys_common/backtrace.rs:61\n   7:     0x559ffa95beed - std::sys_common::backtrace::print::h301abac8bb2e3e81\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/sys_common/backtrace.rs:48\n   8:     0x559ffa95beed - std::panicking::default_hook::{{closure}}::hde0cb80358a6920a\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:208\n   9:     0x559ffa95bb98 - std::panicking::default_hook::h9b1a691049a0ec8f\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:227\n  10:     0x559ffa95c5d1 - std::panicking::rust_panic_with_hook::h2bdec87b60580584\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:577\n  11:     0x559ffa95c179 - std::panicking::begin_panic_handler::{{closure}}::h101ca09d9df5db47\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:484\n  12:     0x559ffa95a22c - std::sys_common::backtrace::__rust_end_short_backtrace::h3bb85654c20113ca\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/sys_common/backtrace.rs:153\n  13:     0x559ffa95c139 - rust_begin_unwind\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:483\n  14:     0x559ffa95c0eb - std::panicking::begin_panic_fmt::hf0503558fbe5b251\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:437\n  15:     0x559ffa957022 - std::io::stdio::print_to::h9435376f36962f3f\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/io/stdio.rs:993\n  16:     0x559ffa957022 - std::io::stdio::_print::h0d31d4b9faa6e1ec\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/io/stdio.rs:1005\n  17:     0x559ffa944807 - wordstats::main::h1c2ea6400047a5eb\n  18:     0x559ffa942e73 - std::sys_common::backtrace::__rust_begin_short_backtrace::h9e31cf87ddc88116\n  19:     0x559ffa942e49 - std::rt::lang_start::{{closure}}::h6c6491f05894818f\n  20:     0x559ffa95c9f7 - core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::he179d32a5d10d957\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/ops/function.rs:259\n  21:     0x559ffa95c9f7 - std::panicking::try::do_call::hcb3d5e7be089b2b4\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:381\n  22:     0x559ffa95c9f7 - std::panicking::try::h7ac93b0cd56fb701\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:345\n  23:     0x559ffa95c9f7 - std::panic::catch_unwind::h7b40e396c93a4fcd\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panic.rs:382\n  24:     0x559ffa95c9f7 - std::rt::lang_start_internal::h142b9cc66267fea1\n                               at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/rt.rs:51\n  25:     0x559ffa944ae2 - main\n  26:     0x7f6223a380b3 - __libc_start_main\n  27:     0x559ffa94209e - _start\n  28:                0x0 - <unknown>\n\n
Run Code Online (Sandbox Code Playgroud)\n

当我编译这个程序时

\n
use diacritics;\nuse std::collections::HashMap;\nuse std::io;\nuse std::io::prelude::*;\n\n#[derive(Debug)]\nstruct Entry {\n    word: String,\n    count: u32,\n}\n\nstatic SEPARATORS: &\'static [char] = &[\n    \' \', \',\', \'.\', \'!\', \'?\', \'\\\'\', \'"\', \'\\n\', \'(\', \')\', \'#\', \'{\', \'}\', \'[\', \']\', \'-\', \';\', \':\',\n];\n\nfn main() {\n    let mut words: HashMap<String, u32> = HashMap::new();\n    let stdin = io::stdin();\n    for line in stdin.lock().lines() {\n        line_processor(line.unwrap(), &mut words)\n    }\n    output(&mut words);\n}\n\nfn line_processor(line: String, words: &mut HashMap<String, u32>) {\n    let formatted_line;\n    let mut word = String::new();\n    formatted_line = diacritics::remove_diacritics(&line).to_lowercase();\n\n    for c in formatted_line.chars() {\n        if SEPARATORS.contains(&c) {\n            add_word(word, words);\n            word = String::new();\n        } else {\n            word.push_str(&c.to_string());\n        }\n    }\n}\n\nfn add_word(word: String, words: &mut HashMap<String, u32>) {\n    if word.len() > 0 {\n        if words.contains_key::<str>(&word) {\n            words.insert(word.to_string(), words.get(&word).unwrap() + 1);\n        } else {\n            words.insert(word.to_string(), 1);\n        }\n        // println!("word >{}<", word.to_string())\n    }\n}\n\nfn output(words: &mut HashMap<String, u32>) {\n    let mut stack = Vec::<Entry>::new();\n\n    for (k, v) in words {\n        stack.push(Entry {\n            word: k.to_string(),\n            count: *v,\n        });\n    }\n\n    stack.sort_by(|a, b| b.count.cmp(&a.count));\n    stack.reverse();\n\n    while let Some(entry) = stack.pop() {\n        println!("{}\\t{}", entry.count, entry.word);\n    }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

这边走 :

\n
cargo build --release\n
Run Code Online (Sandbox Code Playgroud)\n

我这样运行程序:

\n
cargo build --release\n
Run Code Online (Sandbox Code Playgroud)\n

这个程序应该只显示类似这样的内容(最高字数),没有任何痕迹:

\n
cat src/sample.txt | ./target/release/wordstats  | head -n 50\n
Run Code Online (Sandbox Code Playgroud)\n

这是一些回显内容或一些其他文件(例如)的情况cat src/main.rs | ...\n但不适用于作为随机维基百科页面一部分的此文件内容。

\n

我的程序是一个愚蠢的字数统计程序,仅打印表格排序的键值列表。

\n

当我在程序中通过管道传输结果时会出现此问题,head -n 50但在打印完整输出时不会出现此问题

\n

知道为什么我会得到这样的痕迹吗?我在程序中处理某些问题的方式是否错误,或者是否与其他问题有关(rust lib / unix 不当行为)

\n

我的 rustc 版本是:rustc 1.48.0 (7eac88abb 2020-11-16)

\n

编辑 :

\n

添加缺少的 Cargo.toml

\n
[package]\nname = "wordstats"\nversion = "0.1.0"\nauthors = ["Eric R\xc3\xa9gnier <utopman@gmail.com>"]\nedition = "2018"\n\n[dependencies]\ndiacritics = "0.1.1"\n
Run Code Online (Sandbox Code Playgroud)\n

Bur*_*hi5 12

首先,您没有提供足够的信息来重现您的问题。您提供了使用第三方依赖项的源代码,但忽略了提供Cargo.toml. 就您而言,删除依赖项的使用非常容易,而不会影响手头的问题,所以这就是我所做的。

其次,println!正是由于这个原因,在非玩具命令行程序中使用是一把枪。也就是说,有两个问题结合在一起会产生这种不良行为:

  1. println!如果写入标准输出时发生任何错误,将会出现恐慌。
  2. Rust 运行时所做的几件事之一是忽略 SIGPIPE,这意味着对PIPE已关闭的文件描述符的相应写入将返回错误,而不是向您的应用程序发送信号。(在该链接中,您可以看到我记录在案,主张改变这种行为。)

在典型的 C 程序中,SIGPIPE不会被忽略。通常也不会明确处理它。当进程收到一个它无法处理的信号时,该进程就会终止。在这种情况下,这正是您想要的。一旦head停止读取其标准输入(您的标准输出),您希望程序停止,但您也希望它优雅地停止,而不会惊慌或打印错误。因为这就是 Unix CLI 实用程序的作用。

您有两种方法可以解决这个问题。一种方法是更改​​代码以BrokenPipe显式处理错误。您的代码的编写方式就好像错误不会发生一样,因为您是unwrap读取标准输出的结果。所以你的程序不符合习惯,也没有设置来处理错误。因此,为了BrokenPipe正确处理,我必须进行一些小更改,以便它正确地冒出错误:

use std::collections::HashMap;
use std::io;
use std::io::prelude::*;

#[derive(Debug)]
struct Entry {
    word: String,
    count: u32,
}

static SEPARATORS: &'static [char] = &[
    ' ', ',', '.', '!', '?', '\'', '"', '\n', '(', ')', '#', '{', '}', '[', ']', '-', ';', ':',
];

fn main() {
    if let Err(err) = try_main() {
        if err.kind() == std::io::ErrorKind::BrokenPipe {
            return;
        }
        // Ignore any error that may occur while writing to stderr.
        let _ = writeln!(std::io::stderr(), "{}", err);
    }
}

fn try_main() -> Result<(), std::io::Error> {
    let mut words: HashMap<String, u32> = HashMap::new();
    let stdin = io::stdin();
    for result in stdin.lock().lines() {
        let line = result?;
        line_processor(line, &mut words)
    }
    output(&mut words)?;
    Ok(())
}

fn line_processor(line: String, words: &mut HashMap<String, u32>) {
    let mut word = String::new();

    for c in line.chars() {
        if SEPARATORS.contains(&c) {
            add_word(word, words);
            word = String::new();
        } else {
            word.push_str(&c.to_string());
        }
    }
}

fn add_word(word: String, words: &mut HashMap<String, u32>) {
    if word.len() > 0 {
        if words.contains_key::<str>(&word) {
            words.insert(word.to_string(), words.get(&word).unwrap() + 1);
        } else {
            words.insert(word.to_string(), 1);
        }
        // println!("word >{}<", word.to_string())
    }
}

fn output(words: &mut HashMap<String, u32>) -> Result<(), std::io::Error> {
    let mut stack = Vec::<Entry>::new();

    for (k, v) in words {
        stack.push(Entry {
            word: k.to_string(),
            count: *v,
        });
    }

    stack.sort_by(|a, b| b.count.cmp(&a.count));
    stack.reverse();

    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    while let Some(entry) = stack.pop() {
        writeln!(stdout, "{}\t{}", entry.count, entry.word)?;
    }
    Ok(())
}
Run Code Online (Sandbox Code Playgroud)

处理此问题的第二种方法是返回到 的默认行为SIGPIPE。这将使您的 Rust 应用程序表现得像 C 应用程序。SIGPIPE这可以通过定义一个函数来重置to的信号处理程序来完成SIG_DFL

#[cfg(unix)]
fn reset_sigpipe() {
    unsafe {
        libc::signal(libc::SIGPIPE, libc::SIG_DFL);
    }
}

#[cfg(not(unix))]
fn reset_sigpipe() {
    // no-op
}
Run Code Online (Sandbox Code Playgroud)

然后将其称为 中的第一件事main。然后您可以删除对BrokenPipe错误的任何特定处理,因为它不会发生。相反,您的进程将收到一个PIPE信号,并且随后将终止。这是完整的代码:

use std::collections::HashMap;
use std::io;
use std::io::prelude::*;

#[derive(Debug)]
struct Entry {
    word: String,
    count: u32,
}

static SEPARATORS: &'static [char] = &[
    ' ', ',', '.', '!', '?', '\'', '"', '\n', '(', ')', '#', '{', '}', '[', ']', '-', ';', ':',
];

fn main() {
    if let Err(err) = try_main() {
        let _ = writeln!(std::io::stderr(), "{}", err);
    }
}

fn try_main() -> Result<(), std::io::Error> {
    reset_sigpipe();
    
    let mut words: HashMap<String, u32> = HashMap::new();
    let stdin = io::stdin();
    for result in stdin.lock().lines() {
        let line = result?;
        line_processor(line, &mut words)
    }
    output(&mut words)?;
    Ok(())
}

fn line_processor(line: String, words: &mut HashMap<String, u32>) {
    let mut word = String::new();

    for c in line.chars() {
        if SEPARATORS.contains(&c) {
            add_word(word, words);
            word = String::new();
        } else {
            word.push_str(&c.to_string());
        }
    }
}

fn add_word(word: String, words: &mut HashMap<String, u32>) {
    if word.len() > 0 {
        if words.contains_key::<str>(&word) {
            words.insert(word.to_string(), words.get(&word).unwrap() + 1);
        } else {
            words.insert(word.to_string(), 1);
        }
        // println!("word >{}<", word.to_string())
    }
}

fn output(words: &mut HashMap<String, u32>) -> Result<(), std::io::Error> {
    let mut stack = Vec::<Entry>::new();

    for (k, v) in words {
        stack.push(Entry {
            word: k.to_string(),
            count: *v,
        });
    }

    stack.sort_by(|a, b| b.count.cmp(&a.count));
    stack.reverse();

    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    while let Some(entry) = stack.pop() {
        writeln!(stdout, "{}\t{}", entry.count, entry.word)?;
    }
    Ok(())
}


#[cfg(unix)]
fn reset_sigpipe() {
    unsafe {
        libc::signal(libc::SIGPIPE, libc::SIG_DFL);
    }
}

#[cfg(not(unix))]
fn reset_sigpipe() {
    // no-op
}
Run Code Online (Sandbox Code Playgroud)