如何从stdin读取与OS兼容的字符串?

时间:2016-11-06 22:36:01

标签: rust

我试图编写一个Rust程序,在stdin上获取一个单独的文件名列表。

在Windows上,我可以从cmd窗口调用它,例如:

dir /b /s | findstr .*,v$ | rust-prog -n

在Unix上我会使用类似的东西:

find . -name '*,v' -print0 | rust-prog -0

我无法将stdin上收到的内容转换为std::path::Path可以使用的内容。据我所知,要获得可在Windows或Unix上编译的内容,我将需要使用条件编译,并std::os::windows::ffistd::os::unix::ffi

此外,在Windows上我似乎需要使用kernel32::MultiByteToWideChar使用当前代码页来创建std::os::windows::ffi::OsStrExt可用的内容。

有更简单的方法吗?我所建议的甚至看起来是否可行?

例如,将字符串转换为路径很容易,因此我尝试使用stdin的字符串处理函数:

use std::io::{self, Read};
fn main() {
    let mut buffer = String::new();
    match io::stdin().read_line(&mut buffer) {
        Ok(n) => println!("{}", buffer),
        Err(error) => println!("error: {}", error)
    }
}

在Windows上,如果我的目录中包含一个名为¿.txt的文件(即0xbf)。并将名称输入stdin。我得到:error: stream did not contain valid UTF-8

1 个答案:

答案 0 :(得分:1)

这是Windows的合理外观版本。使用win32api函数将控制台提供的字符串转换为宽字符串,然后使用OsString::from_wide将其包装在OsString中。

我不相信它还使用了正确的代码页。 dir似乎使用OEM代码页,所以也许这应该是默认代码。在控制台中输入代码页和输出代码页之间也有区别。

在我的Cargo.toml

[dependencies]
winapi = "0.2"
kernel32-sys = "0.2.2"

根据问题读取在Windows上通过stdin传输的文件名列表的代码。

extern crate kernel32;
extern crate winapi;

use std::io::{self, Read};
use std::ptr;
use std::fs::metadata;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;

/// Convert windows console input to wide string that can
/// be used by OS functions
fn wide_from_console_string(bytes: &[u8]) -> Vec<u16> {
    assert!(bytes.len() < std::i32::MAX as usize);
    let mut wide;
    let mut len;
    unsafe {
        let cp = kernel32::GetConsoleCP();
        len = kernel32::MultiByteToWideChar(cp, 0, bytes.as_ptr() as *const i8, bytes.len() as i32, ptr::null_mut(), 0);
        wide = Vec::with_capacity(len as usize);
        len = kernel32::MultiByteToWideChar(cp, 0, bytes.as_ptr() as *const i8, bytes.len() as i32, wide.as_mut_ptr(), len);
        wide.set_len(len as usize);
    }
    wide
}

/// Extract paths from a list supplied as Cr LF
/// separated wide string
/// Would use a generic split on substring if it existed
fn paths_from_wide(wide: &[u16]) -> Vec<OsString> {
    let mut r = Vec::new();
    let mut start = 0;
    let mut i = start;
    let len = wide.len() - 1;
    while i < len {
        if wide[i] == 13 && wide[i + 1]  == 10 {
            if i > start {
                r.push(OsString::from_wide(&wide[start..i]));
            }
            start = i + 2;
            i = i + 2;
        } else {
            i = i + 1;
        }
    }
    if i > start {
        r.push(OsString::from_wide(&wide[start..i]));
    }
    r
}

fn main() {
    let mut bytes = Vec::new();
    if let Ok(_) = io::stdin().read_to_end(&mut bytes) {
        let pathlist = wide_from_console_string(&bytes[..]);
        let paths = paths_from_wide(&pathlist[..]);
        for path in paths {
            match metadata(&path) {
                Ok(stat) => println!("{:?} is_file: {}", &path, stat.is_file()),
                Err(e) => println!("Error: {:?} for {:?}", e, &path)
            }
        }
    }
}