1

I am looking at examples but they all work with only one line piped/input into my rust program, but I need 2 lines or more as Strings so I can remove the \r\n or \n or \r and the last . line, so basically I need them in Strings so I can work on them. so here is some of my snips.

 let mut buffer = "".to_string();

    io::stdin().read_to_string(&mut buffer)?;
    print!("{}",remove(buffer));
// remove() is  the fn where I am trying to work with the Strings
...

Above I am reading everything I pipe to my program from stdin. cat test.txt > cargo run that works.. but I am pipping this input:

ClientAuthname: user\r\n
ClientPassword: pass\r\n
.\r\n

so I have made and change like 20 something functions under remove() and cant seem to get it right, rust complainsts about it one way or another, I have tested many examples but they are for u* types or str types etc. when using String mutable or not I always get issues. Some of the solutions I been trying for example this ones Remove single trailing newline from String without cloning

I can't get anything to work with my case. I will appreciate if someone can give me a hand and give a explanation so I learn this since I do a lot of unix systems programming on C/Go and moving my programs to rust.

Thanks [edit] Since people wants me to waste text and post examples of what I tried this are 3 of like 14 or so examples I have tried so far

/*
fn remove(arg: String) -> String {
    let newarg = arg.trim_end().to_string();
    return newarg;
}
*/
/*
fn parse(s: &mut String) -> String {
    if s.ends_with('\n') {
        s.pop();
        if s.ends_with('\r') {
            s.pop();
        }
    }
    return s.to_string();
}
*/

fn parse(s: &mut String) -> String {
    s.strip_suffix("\r\n").or(s.strip_suffix("\n")).unwrap_or(s);
    return s.to_string();
}

none works. they all fail to remove \r\n or \n :(

so what I have as stdin? I tried everything I see! all of this! https://doc.rust-lang.org/stable/std/io/struct.Stdin.html#method.read_line also I tried all of this! How to capture the output of a process piped into a Rust program?

I can keep going! this is C is super simple to do. dont know why I cant get the output from another program the native unix way wit ha simple pipe, like echo "Password\r\n" | cargo run and remove the "\r\n" nothing works if it comes from stdin from another program.

use std::io;

let lines = io::stdin().lines();
for line in lines {
    println!("got a line: {}", line.unwrap());
}

WHY I cant add parse() to the line!!! String and get the \r\n remove with ANY of the examples I find! I change both, the stdin ways and the function to remove, I tried .trim() I tried .pop() I tried .truncate() I tried .strip_suffix("\r\n") !!! everything ! cant someone just write a small snipped that will do exactly what I need since there does not exists any thing that works. ? or at least tell me how I should do it so I do not keep trying and trying and trying everything I see. I am so close to ditch all 1000+ of working code because of something so simple as grabbing and manipulating String from another unix program, that I have done in C thousands of times since Unix works based on pipes.

rek2
  • 101
  • 12
  • Your problem statement is incomprehensible. You wrote "I have made and change like 20 something functions under remove() and cant seem to get it right, rust complainsts about it one way or another" but don't show us any of those variations or the errors you observed. It appears you are asking how to read multiple lines and split the text into individual lines but your problem statement is so confusing that is only a guess. – Kurtis Rader Jul 19 '23 at 04:27
  • Yes I need to get the strings piped to my program, and work on them to remove end lines and new lines etc, clean it so I can then compare the credentials with the ones in postgresql(this is irrevelant) and I do not put example because there are to many, I tried all the ones I can find online on searches none works, so looking for a working example? – rek2 Jul 19 '23 at 04:37
  • I googled "rust split lines" and got a lot of useful examples. Such as those in https://stackoverflow.com/questions/26643688/how-do-i-split-a-string-in-rust. Which tells me you can split a string, `s`, on newlines by doing `s.lines()`. – Kurtis Rader Jul 19 '23 at 04:46
  • You can also trim all edge whitespace with `str::trim` – PatientPenguin Jul 19 '23 at 06:31
  • I do not need to "split" at least not yet, first I need it on "String" not str or bytes, and I need to remove the end of lines... that is my issue, not split. str is not String... I need it to ber Strings right?, to Kurtis I tried all of those examples, none of Strings they all convert to bytes or str – rek2 Jul 19 '23 at 13:27
  • 1
    `String` derefs to `str`, which means that you can call `str` methods on a `String`. This means that you can do something like `buffer.trim().to_string()`, which will provide you the strings without the trailing whitespace. This behavior can be seen in the [first chapter](https://doc.rust-lang.org/stable/book/ch02-00-guessing-game-tutorial.html) of the [Rust book](https://doc.rust-lang.org/stable/book/title-page.html) – PatientPenguin Jul 19 '23 at 13:47
  • 1
    Thanks @PatienPenguin but again, all that is all good and easy, the problem is the way I use stdin in ALL the examples always get types incompatible for me to call line[1] or line[2] as strings to later use .trim() or work on the String. For example, I get now using the book example a Result and is driving me insane because I can't use string actions on it, :( I found ways to do this with 1 line but not multiple lines… I see no example, so I need an example that I can learn by, as I mentioned in my original post. I have use and obviosly before posting here all the examples here – rek2 Jul 19 '23 at 16:40
  • 1
    continue: here: https://doc.rust-lang.org/stable/std/io/struct.Stdin.html#method.read_line but none work for multi line and get the Strings to later use and format, also of course, before I even posted here I tried all the examples I could find around the web with no luck, I got it to work fine when s 1 word like echo "example" | cargo run but when is multi line it puts the arguments on weird types that I cant work with. There is something I am not understanding about rust and stdin in compared with C scan, that Is much simpler to grab from stdin, so this is why I am asking for a example. – rek2 Jul 19 '23 at 16:42
  • Does your input actually have a return character (`\r`) and a newline character (`\n`)? Or does it have the characters `\, r, \, and n`? Seeing the debug representation of the strings you're reading would help a lot. – kmdreko Jul 20 '23 at 14:52
  • Hello @kmdreko, is above on the original post but is frorm here: https://www.eyrie.org/~eagle/software/inn/docs-2.7/external-auth.html is the first point, is telling us what information their program is passing to our authenticator. I am writting one to connecto to postgresql everything works I just need to get this input by stdin – rek2 Jul 20 '23 at 18:18
  • 1
    Again, seeing the debug representation of the strings you're reading would help a lot (i.e. `println!("{buffer:?}")` before attempting to trim it). The spec you linked documents that it expects the CRLF control characters, but you say "I'm piping this input" with the "\r\n" spelled out which makes me think you've done otherwise. – kmdreko Jul 20 '23 at 18:28
  • ohh, got it, sorry I tried many examples, and ways, I do not have a "this is what I have" I have a "this is what I have now, after trying everything I see, read" is why I am asking a "how will you do this" because nothing works for me, I am using every single solution from the rust documentation under the sdtin entry from here https://doc.rust-lang.org/stable/std/io/struct.Stdin.html#method.read_line so pick one, the one I been banging my head more is the one with the '''let lines = io::stdin().lines(); for line in lines { println!("got a line: {}", line.unwrap()); }''' but tried all – rek2 Jul 20 '23 at 18:38
  • this person had similar issues trying to get stdin into a rust program, I find hard to believe that rust makes it so hard for something so simple on C/C++ like a simple unix program sending stdout to another one (mine) echo "Password\r\n" | cargo run and not working correctly when is more than 1 line... https://stackoverflow.com/questions/66260204/rust-non-interactive-stdin-read – rek2 Jul 21 '23 at 03:38
  • I also tried tonight like 5-6 other methods still none really works, they print the output with I cant do anything with "each" string line, like remove \r\n I tried this https://stackoverflow.com/questions/49733027/how-to-capture-the-output-of-a-process-piped-into-a-rust-program foind even more example later! and none let me use a simple .trim() succesfully! :( Im really close to rewrite the 1203 lines of my working rust code to C because of this small issue that should be simple to do. – rek2 Jul 21 '23 at 03:40
  • Would a simple `tim_end()` be enough? (as [in this example](https://www.rustexplorer.com/b#%2F*%0A%5Bdependencies%5D%0A%0A*%2F%0A%0Afn%20main()%20%7B%0A%20%20%20%20let%20string%20%3D%20%22hello%5Cr%5Cn%22.to_string()%3B%0A%0A%20%20%20%20let%20_res%20%3D%20string.trim_end()%3B%0A%0A%20%20%20%20println!(%22'%7B%7D'%22%2C%20_res)%3B%0A%7D%0A)) – VonC Jul 21 '23 at 05:09

2 Answers2

3

I will focus on this part first:

dont know why I cant get the output from another program the native unix way wit ha simple pipe, like echo "Password\r\n" | cargo run and remove the "\r\n"

When you run echo "Password\r\n", the \r and \n are not interpreted as a carriage return and a line feed. They are interpreted as a literal \, and then a r, and then a \, and then a n. This is four ASCII characters: \x5c\x72\x5c\x6e.

See: Echo newline in Bash prints literal \n for details.

When you write "\r\n" in your Rust code, they are interpreted as a carriage return and line feed. This is two ASCII characters: \x0d\x0a.

They are not the same.

To get your Rust code to match that echo output, you would need to escape the escape character \ like "\\r\\n". Here's a demonstration:

fn main() {
    let mut line = String::new();
    std::io::stdin().read_line(&mut line).unwrap();

    // show the raw debug output for the line
    let line = &line;
    dbg!(line);

    // remove the newline character preserved from `.read_line()`
    let line = line.trim_end();
    dbg!(line);

    // remove the trailing literal "\r\n" you're expecting
    let line = line.trim_end_matches("\\r\\n");
    dbg!(line);
}
> echo "Password\r\n" | cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/temp`
[src/main.rs:7] line = "Password\\r\\n\n"
[src/main.rs:11] line = "Password\\r\\n"
[src/main.rs:15] line = "Password"

The same applies with your cat example, your file does not have the CR (\r) and LF (\n) control characters, you are sending literal \, r, \, and n (and then probably a real \n because there's a newline).


That all being said, you have either misinterpreted the documentation or misunderstood your tools. You linked the spec you're following (in a comment) as: https://www.eyrie.org/~eagle/software/inn/docs-2.7/external-auth.html. Within it, it says:

[...] it passes information on standard input as a sequence of key: value lines. Each line ends with CRLF [...]

So simulating this via echo "Password\r\n" is incorrect and not following the spec. To do the correct thing using echo, you can pass -e (shown in the SO link above) and you'll see the behavior is quite different:

> echo -e "Password\r\n" | cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/temp`
[src/main.rs:7] line = "Password\r\n"
[src/main.rs:11] line = "Password"
[src/main.rs:15] line = "Password"

So a simple .trim()/.trim_end() is enough to do the job.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Thank you so much! now I see where my logic was doom to fail, and you pointed out exacly the 2 pieces I had wrong and explained why, so I can see the issue and learn from my mistake, thank you!. I did not got right the original spec, as you mention, also I was not using echo the righ way. – rek2 Jul 22 '23 at 04:15
  • Nice work @kmdreko , I would suggest to move the code to a separate function that returns a `std::io::Result` so it is easier to handle the error and you can use `?` operator – PXP9 Jul 22 '23 at 06:37
  • I have found one problem, this solution does not work if `user` and `pass` are in different lines. The reason is becaise `std::io::stdin().read_line(&mut line).unwrap();` , stops reading when he find a `\n` so it does not pick second argument which is in a different line. `echo -e "user\r\npass\r\n" | cargo r` with this call only the `user` is captured , `pass` data is lost. – PXP9 Jul 22 '23 at 06:52
  • @PXP9 the code I provided is only for demonstration purposes and was minimized to only express what was needed to address OPs confusion and frustration. It is not intended to be a complete parser. – kmdreko Jul 22 '23 at 13:30
2

I have been testing this solution. That only calls once to read. This will read full output and will delete all \r and \n and put all the strings in a Vec

use std::io::Read;

fn parse() -> std::io::Result<Vec<String>> {
    let mut line = String::new();
    std::io::stdin().read_to_string(&mut line)?;

    // show the raw debug output for the line
    let line = &line;
    dbg!(line);

    // remove the newline character preserved from `.read_line()`
    let line = line.trim_end();
    dbg!(line);

    // remove the trailing literal "\r\n" you're expecting
    let lines: Vec<String> = line.split("\r\n").map(|str| str.to_string()).collect();
    dbg!(&lines);

    Ok(lines)
}

fn main() {
    let lines = match parse() {
        Ok(val) => val,
        Err(err) => panic!("Some input error : {}", err),
    };

    print!("Lines : {lines:?}");
    // use them here
}

text test file

Output code with changes

% meaning in the shell means End of file therefore it is not taking \r or \n char.

Edit : Some changes to make it work with multiple lines and put it in a Vec.

PXP9
  • 358
  • 1
  • 8
  • Thank you! your solution helped me and works great but I have to give as correct the other answer since he when ahead and explained what was wrong about my logic and why it was failing. Thank you if I could I will have add both as correct.! <3 – rek2 Jul 22 '23 at 04:17