7
\$\begingroup\$

I rewrote echo from the manpage to learn Rust, and got the following:

// echo in rust - display a line of text
use std::env;
static HELP: &'static str = "\
echo the STRING(s) to standard output.\n\
-n do not output the trailing newline\n\
-e enable intepretation of backslash escapes\n\
-E disable intepretation of backslash escapes (default)\n\
--help display this help and exit
If -e is in effect, the following sequence are recognized:
 \\\\ backslash
 \\a alert (BEL)
 \\b backspace
 \\c produce no futher output
 \\e escape
 \\f form feed
 \\n new line
 \\r carriage return
 \\t horizontal tab
 \\v vertical tab";
fn main() {
 let mut args: Vec<String> = env::args().skip(1).collect();
 let mut nl: bool = true; // the "-n" flag can be used to indicate no new line
 let mut escape: bool = false; // Disable interpreation of backslashes
 let mut args_count: usize = 0; // first argument is binary name
 for stuff in &args {
 if !stuff.starts_with("-") {
 break;
 }
 if stuff == "-n" {
 nl = false;
 args_count += 1;
 }
 if stuff == "-e" {
 escape = true;
 args_count += 1;
 }
 if stuff == "-E" {
 escape = false;
 args_count += 1;
 }
 if stuff == "--help" {
 println!("{}", HELP);
 return;
 }
 }
 if escape {
 for stuff in &mut args[args_count..] {
 *stuff = stuff.replace("\\\\", "\\"); // backslashe
 *stuff = stuff.replace("\\a", ""); // alert (BEL)
 *stuff = stuff.replace("\\b", ""); // backspace
 *stuff = stuff.replace("\\c", ""); // produce no further output
 *stuff = stuff.replace("\\e", ""); // escape
 *stuff = stuff.replace("\\f", ""); // form feed
 *stuff = stuff.replace("\\n", ""); // new line
 *stuff = stuff.replace("\\r", ""); // carriage return
 *stuff = stuff.replace("\\t", ""); // horizontal tab
 *stuff = stuff.replace("\\v", ""); // vertical tab
 }
 }
 print!("{}", args[args_count..].join(" "));
 if nl {
 println!();
 }
}

I then had a look at previously asked questions on this topic, and learnt of skip from here.

I would particularly welcome feedback on string manipulation, notably on the argument parsing (i.e. here, I expect known flags before data to echo), and the if escape block.

Could the argument parsing be converted to a match statement, whilst retaining the arg_count increment and the break condition?

Another thing I'm unsure of is that I'm first collecting args and then checking each element within the vector and eventually joining it, using a bunch of for loops.

Is there a more idiomatic way to do so, such as vector subtraction or...?

Lastly, my manpage for echo mentions support for decoding bytes:

0円NNN byte with octal value NNN (1 to 3 digits)
\xHH byte with hexadecimal value HH (1 to 2 digits)

Would you have pointers on how to do this type of operation?

Shepmaster
8,79827 silver badges28 bronze badges
asked Oct 9, 2019 at 13:35
\$\endgroup\$
2
  • 1
    \$\begingroup\$ As described in Rust Echo Command Implementation, you really should not collect all of the input as a vector as it's very wasteful. \$\endgroup\$ Commented Oct 9, 2019 at 14:14
  • 1
    \$\begingroup\$ @Shepmaster That can even make the program crash in case of infinite input. \$\endgroup\$ Commented Oct 9, 2019 at 14:17

1 Answer 1

2
\$\begingroup\$

Thanks to @Shepmaster's comment referring to one of his previous answers, I was able to make the process nicer, by using env::args as an Iterator.

By doing so, it became much easier to switch to using a match statement, and avoids all the string concatenation I mentioned in my question.

I've also managed to add the conversion from hex and octal values to chars by making use of std::u8::from_str_radix in conjunction with std::string::String::trim_start_matches

I therefore now have the following:

// echo in rust - display a line of text
use std::env;
use std::u8;
static HELP: &'static str = "\
echo the STRING(s) to standard output.\n\
-n do not output the trailing newline\n\
-e enable intepretation of backslash escapes\n\
-E disable intepretation of backslash escapes (default)\n\
--help display this help and exit
If -e is in effect, the following sequence are recognized:
 \\\\ backslash
 \\a alert (BEL)
 \\b backspace
 \\c produce no futher output
 \\e escape
 \\f form feed
 \\n new line
 \\r carriage return
 \\t horizontal tab
 \\v vertical tab
 \0円NNN byte with octal value NNN (1 to 3 digits)
 \\xHH byte with hexadecimal value HH (1 to 2 digits)
";
fn main() {
 let mut nl: bool = true; // the "-n" flag can be used to indicate no new line
 let mut escape: bool = false; // Disable interpretation of backslashes
 let mut args_done = false; // Argument parsing done
 let mut first = true; // Correct the printing spacing
 for arg in env::args().skip(1) {
 if !args_done {
 match arg.as_ref() {
 "-n" => nl = false,
 "-e" => escape = true,
 "-E" => escape = false,
 "--help" => print!("{}", HELP),
 _ => args_done = true,
 }
 }
 if args_done {
 let mut datum: String = arg;
 if escape {
 datum = datum.replace("\\\\", "\\"); // backslash
 datum = datum.replace("\\a", ""); // alert (BEL)
 datum = datum.replace("\\b", ""); // backspace
 datum = datum.replace("\\c", ""); // produce no further output
 datum = datum.replace("\\e", ""); // escape
 datum = datum.replace("\\f", ""); // form feed
 datum = datum.replace("\\n", ""); // new line
 datum = datum.replace("\\r", ""); // carriage return
 datum = datum.replace("\\t", ""); // horizontal tab
 datum = datum.replace("\\v", ""); // vertical tab
 if datum.starts_with("\\x") && 3 <= datum.len() && datum.len() <= 4 {
 // Hex values with at most 2 digits
 let value = datum.trim_start_matches("\\x");
 let chr = u8::from_str_radix(value, 16).unwrap() as char;
 datum = chr.to_string();
 }
 if datum.starts_with("\0円") && 3 <= datum.len() && datum.len() <= 5 {
 // Octal values with at most 3 digits
 let value = datum.trim_start_matches("\0円");
 let chr = u8::from_str_radix(value, 8);
 // The maximum octal value for a byte is 377.
 // Check that this conversion was successful.
 if chr.is_ok() {
 let x = chr.unwrap() as char;
 datum = x.to_string();
 }
 }
 }
 if first {
 print!("{}", datum);
 first = false;
 } else {
 print!(" {}", datum);
 }
 }
 }
 if nl {
 println!();
 }
}

This now adds an extra first flag for correct formatting, but it's okay for what is done here.

answered Oct 9, 2019 at 15:57
\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.