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?
-
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\$Shepmaster– Shepmaster2019年10月09日 14:14:36 +00:00Commented Oct 9, 2019 at 14:14
-
1\$\begingroup\$ @Shepmaster That can even make the program crash in case of infinite input. \$\endgroup\$Boiethios– Boiethios2019年10月09日 14:17:41 +00:00Commented Oct 9, 2019 at 14:17
1 Answer 1
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.