Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions src/uu/split/src/split.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ fn filter_args(
filter
}

/// Short options that take a value, as in '-b 10' or '-b10'
const SHORT_OPTS_WITH_VALUE: &[u8] = b"abClnt"; // spell-checker:disable-line

/// Helper function to [`filter_args`]
/// Checks if the slice is a true short option (and not hyphen prefixed value of an option)
/// and if so, a short option that can contain obsolete lines value
Expand All @@ -151,7 +154,9 @@ fn should_extract_obs_lines(
!preceding_long_opt_req_value
&& !preceding_short_opt_req_value
&& slice.strip_prefix("-").is_some_and(|s| {
!s.as_bytes().first().is_some_and(|s| b"-abClnt".contains(s)) // spell-checker:disable-line
!s.as_bytes()
.first()
.is_some_and(|s| *s == b'-' || SHORT_OPTS_WITH_VALUE.contains(s))
})
}

Expand All @@ -161,9 +166,18 @@ fn should_extract_obs_lines(
fn handle_extract_obs_lines(slice: &str, obs_lines: &mut Option<String>) -> Option<OsString> {
let mut obs_lines_extracted: Vec<char> = vec![];
let mut obs_lines_end_reached = false;
let mut option_value_reached = false;
let filtered_slice: Vec<char> = slice
.chars()
.filter(|c| {
// The rest of the argument after an option that takes a value,
// as in '-dn10', is that value and not an obsolete lines value
if option_value_reached {
return true;
}
if u8::try_from(*c).is_ok_and(|c| SHORT_OPTS_WITH_VALUE.contains(&c)) {
option_value_reached = true;
}
// To correctly process scenario like '-x200a4'
// we need to stop extracting digits once alphabetic character is encountered
// after we already have something in obs_lines_extracted
Expand Down Expand Up @@ -225,10 +239,21 @@ fn handle_preceding_options(
| options::SEPARATOR
);
}
// capture if current slice is a preceding short option that requires value and does not have value in the same slice (value separated by whitespace)
// capture if current slice is a short option, alone or ending a group like '-dl', that requires value and does not have value in the same slice (value separated by whitespace)
// following slice should be treaded as value for this option
// even if it starts with '-' (which would be treated as hyphen prefixed value)
*preceding_short_opt_req_value = matches!(slice, "-b" | "-C" | "-l" | "-n" | "-a" | "-t");
// the first short option in the group that requires value takes the rest of the group as its value,
// so only a group where that option is the last character leaves the following slice to it
*preceding_short_opt_req_value = slice
.strip_prefix('-')
.filter(|shorts| !shorts.starts_with('-'))
.and_then(|shorts| {
shorts
.bytes()
.position(|c| SHORT_OPTS_WITH_VALUE.contains(&c))
.map(|i| i == shorts.len() - 1)
})
.unwrap_or(false);
}

/// Parameters that control how a file gets split.
Expand Down
48 changes: 48 additions & 0 deletions tests/by-util/test_split.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,28 @@ fn test_split_obs_lines_within_combined_shorts() {
assert_eq!(glob.collate(), at.read_bytes(name));
}

/// Digits that are the value of an option in the same combined short options,
/// as in `-dn10`, are not an obsolete lines option
#[test]
fn test_split_option_value_within_combined_shorts() {
let (at, mut ucmd) = at_and_ucmd!();
let name = "option-value-within-shorts";
RandomFile::new(&at, name).add_lines(100);

ucmd.args(&["-dn10", name]).succeeds().no_output();
let glob = Glob::new(&at, ".", r"x\d\d$");
assert_eq!(glob.count(), 10);
assert_eq!(glob.collate(), at.read_bytes(name));

let (at, mut ucmd) = at_and_ucmd!();
RandomFile::new(&at, name).add_lines(100);

ucmd.args(&["-xl25", name]).succeeds().no_output();
let glob = Glob::new(&at, ".", r"x[[:xdigit:]]{2}$");
assert_eq!(glob.count(), 4);
assert_eq!(glob.collate(), at.read_bytes(name));
}

/// Test for obsolete lines option as part of combined short options with tailing suffix length with value
#[test]
fn test_split_obs_lines_within_combined_shorts_tailing_suffix_length() {
Expand Down Expand Up @@ -637,6 +659,32 @@ fn test_split_obs_lines_as_other_option_value() {
.stderr_contains("split: invalid number of chunks: '-e200'\n");
}

/// A short option that requires value at the end of combined short options, as in `-dl`,
/// takes the following hyphen prefixed argument as its value
#[test]
fn test_split_obs_lines_as_value_of_option_ending_combined_shorts() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("file");

scene
.ucmd()
.args(&["-dl", "-200", "file"])
.fails_with_code(1)
.stderr_only("split: invalid number of lines: '-200'\n");
scene
.ucmd()
.args(&["-xa", "-200", "file"])
.fails_with_code(1)
.stderr_only("split: invalid suffix length: '-200'\n");
// `-a` takes `2l` as its value here, so `-5` is not the value of `-l`
scene
.ucmd()
.args(&["-a2l", "-5", "file"])
.fails_with_code(1)
.stderr_only("split: invalid suffix length: '2l'\n");
}

#[test]
fn test_split_chunks_by_line_or_round_robin_zero_chunks() {
let scene = TestScenario::new(util_name!());
Expand Down
Loading