Skip to content

Commit 3e15738

Browse files
committed
cut: fix -s flag ignored when delimiter is newline
When using newline as the delimiter (-d $'\n') with -s (only-delimited), cut should suppress lines that do not contain the delimiter. However, cut_fields_newline_char_delim() was not checking the only_delimited flag, causing it to always output even when -s was specified. The fix uses read_until() instead of split() to read segments. Unlike split(), read_until() includes the delimiter in the buffer when found, allowing us to detect whether a delimiter was actually present or if we just hit EOF. This commit: - Adds only_delimited parameter to cut_fields_newline_char_delim() - Uses read_until() to detect delimiter presence while reading - If no delimiter found and only_delimited is true, returns early - Adds test case for newline delimiter with -s flag Fixes #10012
1 parent 81ee8d1 commit 3e15738

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

src/uu/cut/src/cut.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,34 @@ fn cut_fields_newline_char_delim<R: Read, W: Write>(
265265
reader: R,
266266
out: &mut W,
267267
ranges: &[Range],
268+
only_delimited: bool,
268269
newline_char: u8,
269270
out_delim: &[u8],
270271
) -> UResult<()> {
271-
let buf_in = BufReader::new(reader);
272+
let mut buf_in = BufReader::new(reader);
273+
let mut segments: Vec<Vec<u8>> = Vec::new();
274+
let mut found_delimiter = false;
275+
276+
// Read segments using read_until, which includes the delimiter in the buffer
277+
// This lets us detect whether a delimiter was actually found
278+
loop {
279+
let mut segment = Vec::new();
280+
if buf_in.read_until(newline_char, &mut segment)? == 0 {
281+
break;
282+
}
283+
// If segment ends with delimiter, we found one - remove it from segment
284+
if segment.last() == Some(&newline_char) {
285+
found_delimiter = true;
286+
segment.pop();
287+
}
288+
segments.push(segment);
289+
}
290+
291+
// With -s (only_delimited), suppress output if no delimiter found
292+
if only_delimited && !found_delimiter {
293+
return Ok(());
294+
}
272295

273-
let segments: Vec<_> = buf_in.split(newline_char).filter_map(|x| x.ok()).collect();
274296
let mut print_delim = false;
275297

276298
for &Range { low, high } in ranges {
@@ -303,7 +325,14 @@ fn cut_fields<R: Read, W: Write>(
303325
match field_opts.delimiter {
304326
Delimiter::Slice(delim) if delim == [newline_char] => {
305327
let out_delim = opts.out_delimiter.unwrap_or(delim);
306-
cut_fields_newline_char_delim(reader, out, ranges, newline_char, out_delim)
328+
cut_fields_newline_char_delim(
329+
reader,
330+
out,
331+
ranges,
332+
field_opts.only_delimited,
333+
newline_char,
334+
out_delim,
335+
)
307336
}
308337
Delimiter::Slice(delim) => {
309338
let matcher = ExactMatcher::new(delim);

tests/by-util/test_cut.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,25 @@ fn test_newline_as_delimiter_with_output_delimiter() {
301301
.stdout_only_bytes("a:b\n");
302302
}
303303

304+
#[test]
305+
fn test_newline_as_delimiter_with_only_delimited() {
306+
// When input has no newline delimiter and -s is specified,
307+
// the line should be suppressed (no output)
308+
new_ucmd!()
309+
.args(&["-f1", "-d", "\n", "-s"])
310+
.pipe_in("abc")
311+
.succeeds()
312+
.stdout_only_bytes("");
313+
314+
// When input has newline delimiter and -s is specified,
315+
// it should output the selected field
316+
new_ucmd!()
317+
.args(&["-f1", "-d", "\n", "-s"])
318+
.pipe_in("line1\nline2")
319+
.succeeds()
320+
.stdout_only_bytes("line1\n");
321+
}
322+
304323
#[test]
305324
fn test_multiple_delimiters() {
306325
new_ucmd!()

0 commit comments

Comments
 (0)