Skip to content

Commit

Permalink
Merge pull request #5514 from Luv-Ray/fix-printf-issue5468
Browse files Browse the repository at this point in the history
`printf`: support %q
  • Loading branch information
tertsdiepraam authored Nov 9, 2023
2 parents 91a9145 + fb414ed commit 9946e77
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 7 deletions.
8 changes: 8 additions & 0 deletions src/uu/printf/printf.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ Fields
second parameter is min-width, integer
output below that width is padded with leading zeroes

* `%q`: ARGUMENT is printed in a format that can be reused as shell input, escaping non-printable
characters with the proposed POSIX $'' syntax.

* `%f` or `%F`: decimal floating point value
* `%e` or `%E`: scientific notation floating point value
* `%g` or `%G`: shorter of specially interpreted decimal or SciNote floating point value.
Expand Down Expand Up @@ -181,6 +184,11 @@ All string fields have a 'max width' parameter
still be interpreted and not throw a warning, you will have problems if you use this for a
literal whose code begins with zero, as it will be viewed as in `\\0NNN` form.)

* `%q`: escaped string - the string in a format that can be reused as input by most shells.
Non-printable characters are escaped with the POSIX proposed ‘$''’ syntax,
and shell meta-characters are quoted appropriately.
This is an equivalent format to ls --quoting=shell-escape output.

#### CHAR SUBSTITUTIONS

The character field does not have a secondary parameter.
Expand Down
22 changes: 15 additions & 7 deletions src/uucore/src/lib/features/tokenize/sub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//! Subs which have numeric field chars make use of the num_format
//! submodule
use crate::error::{UError, UResult};
use crate::quoting_style::{escape_name, QuotingStyle};
use itertools::{put_back_n, PutBackN};
use std::error::Error;
use std::fmt::Display;
Expand Down Expand Up @@ -91,7 +92,7 @@ impl Sub {
// for more dry printing, field characters are grouped
// in initialization of token.
let field_type = match field_char {
's' | 'b' => FieldType::Strf,
's' | 'b' | 'q' => FieldType::Strf,
'd' | 'i' | 'u' | 'o' | 'x' | 'X' => FieldType::Intf,
'f' | 'F' => FieldType::Floatf,
'a' | 'A' => FieldType::CninetyNineHexFloatf,
Expand Down Expand Up @@ -189,7 +190,7 @@ impl SubParser {

let mut legal_fields = [
// 'a', 'A', //c99 hex float implementation not yet complete
'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'i', 'o', 's', 'u', 'x', 'X',
'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'i', 'o', 'q', 's', 'u', 'x', 'X',
];
let mut specifiers = ['h', 'j', 'l', 'L', 't', 'z'];
legal_fields.sort_unstable();
Expand Down Expand Up @@ -260,7 +261,6 @@ impl SubParser {
}
x if legal_fields.binary_search(&x).is_ok() => {
self.field_char = Some(ch);
self.text_so_far.push(ch);
break;
}
x if specifiers.binary_search(&x).is_ok() => {
Expand Down Expand Up @@ -331,7 +331,7 @@ impl SubParser {
if (field_char == 's' && self.min_width_tmp == Some(String::from("0")))
|| (field_char == 'c'
&& (self.min_width_tmp == Some(String::from("0")) || self.past_decimal))
|| (field_char == 'b'
|| ((field_char == 'b' || field_char == 'q')
&& (self.min_width_tmp.is_some()
|| self.past_decimal
|| self.second_field_tmp.is_some()))
Expand Down Expand Up @@ -391,6 +391,7 @@ impl Sub {
// if %s just return arg
// if %b use UnescapedText module's unescape-fn
// if %c return first char of arg
// if %q return arg which non-printable characters are escaped
FieldType::Strf | FieldType::Charf => {
match pf_arg {
Some(arg_string) => {
Expand All @@ -404,11 +405,18 @@ impl Sub {
UnescapedText::from_it_core(writer, &mut a_it, true);
None
}
// for 'c': get iter of string vals,
'q' => Some(escape_name(
arg_string.as_ref(),
&QuotingStyle::Shell {
escape: true,
always_quote: false,
show_control: false,
},
)),
// get opt<char> of first val
// and map it to opt<String>
/* 'c' | */
_ => arg_string.chars().next().map(|x| x.to_string()),
'c' => arg_string.chars().next().map(|x| x.to_string()),
_ => unreachable!(),
}
}
None => None,
Expand Down
34 changes: 34 additions & 0 deletions tests/by-util/test_printf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ fn sub_b_string_handle_escapes() {
.stdout_only("hello \tworld");
}

#[test]
fn sub_b_string_validate_field_params() {
new_ucmd!()
.args(&["hello %7b", "world"])
.run()
.stdout_is("hello ")
.stderr_is("printf: %7b: invalid conversion specification\n");
}

#[test]
fn sub_b_string_ignore_subs() {
new_ucmd!()
Expand All @@ -120,6 +129,31 @@ fn sub_b_string_ignore_subs() {
.stdout_only("hello world %% %i");
}

#[test]
fn sub_q_string_non_printable() {
new_ucmd!()
.args(&["non-printable: %q", "\"$test\""])
.succeeds()
.stdout_only("non-printable: '\"$test\"'");
}

#[test]
fn sub_q_string_validate_field_params() {
new_ucmd!()
.args(&["hello %7q", "world"])
.run()
.stdout_is("hello ")
.stderr_is("printf: %7q: invalid conversion specification\n");
}

#[test]
fn sub_q_string_special_non_printable() {
new_ucmd!()
.args(&["non-printable: %q", "test~"])
.succeeds()
.stdout_only("non-printable: test~");
}

#[test]
fn sub_char() {
new_ucmd!()
Expand Down

0 comments on commit 9946e77

Please sign in to comment.