mirror of
https://github.com/uutils/coreutils
synced 2024-12-14 07:12:44 +00:00
Merge pull request #5514 from Luv-Ray/fix-printf-issue5468
`printf`: support %q
This commit is contained in:
commit
9946e77adb
3 changed files with 57 additions and 7 deletions
|
@ -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.
|
||||
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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() => {
|
||||
|
@ -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()))
|
||||
|
@ -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) => {
|
||||
|
@ -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,
|
||||
|
|
|
@ -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!()
|
||||
|
@ -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!()
|
||||
|
|
Loading…
Reference in a new issue