Skip to content

Commit 7bacd84

Browse files
committed
cut: fix handling of newline as delimiter
1 parent f52a11c commit 7bacd84

File tree

2 files changed

+50
-4
lines changed

2 files changed

+50
-4
lines changed

src/uu/cut/src/cut.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use bstr::io::BufReadExt;
99
use clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgMatches, Command};
1010
use std::ffi::OsString;
1111
use std::fs::File;
12-
use std::io::{stdin, stdout, BufReader, BufWriter, IsTerminal, Read, Write};
12+
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, IsTerminal, Read, Write};
1313
use std::path::Path;
1414
use uucore::display::Quotable;
1515
use uucore::error::{set_exit_code, FromIo, UResult, USimpleError};
@@ -267,10 +267,45 @@ fn cut_fields_implicit_out_delim<R: Read, M: Matcher>(
267267
Ok(())
268268
}
269269

270+
fn cut_fields_newline_char_delim<R: Read>(
271+
reader: R,
272+
ranges: &[Range],
273+
newline_char: u8,
274+
out_delim: &[u8],
275+
) -> UResult<()> {
276+
let buf_in = BufReader::new(reader);
277+
let mut out = stdout_writer();
278+
279+
let segments: Vec<_> = buf_in.split(newline_char).filter_map(|x| x.ok()).collect();
280+
let mut print_delim = false;
281+
282+
for &Range { low, high } in ranges {
283+
for i in low..=high {
284+
// "- 1" is necessary because fields start from 1 whereas a Vec starts from 0
285+
if let Some(segment) = segments.get(i - 1) {
286+
if print_delim {
287+
out.write_all(out_delim)?;
288+
} else {
289+
print_delim = true;
290+
}
291+
out.write_all(segment.as_slice())?;
292+
} else {
293+
break;
294+
}
295+
}
296+
}
297+
out.write_all(&[newline_char])?;
298+
Ok(())
299+
}
300+
270301
fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> {
271302
let newline_char = opts.line_ending.into();
272303
let field_opts = opts.field_opts.as_ref().unwrap(); // it is safe to unwrap() here - field_opts will always be Some() for cut_fields() call
273304
match field_opts.delimiter {
305+
Delimiter::Slice(delim) if delim == [newline_char] => {
306+
let out_delim = opts.out_delimiter.unwrap_or(delim);
307+
cut_fields_newline_char_delim(reader, ranges, newline_char, out_delim)
308+
}
274309
Delimiter::Slice(delim) => {
275310
let matcher = ExactMatcher::new(delim);
276311
match opts.out_delimiter {

tests/by-util/test_cut.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,22 @@ fn test_empty_string_as_delimiter_with_output_delimiter() {
288288

289289
#[test]
290290
fn test_newline_as_delimiter() {
291+
for (field, expected_output) in [("1", "a:1\n"), ("2", "b:\n")] {
292+
new_ucmd!()
293+
.args(&["-f", field, "-d", "\n"])
294+
.pipe_in("a:1\nb:")
295+
.succeeds()
296+
.stdout_only_bytes(expected_output);
297+
}
298+
}
299+
300+
#[test]
301+
fn test_newline_as_delimiter_with_output_delimiter() {
291302
new_ucmd!()
292-
.args(&["-f", "1", "-d", "\n"])
293-
.pipe_in("a:1\nb:")
303+
.args(&["-f1-", "-d", "\n", "--output-delimiter=:"])
304+
.pipe_in("a\nb\n")
294305
.succeeds()
295-
.stdout_only_bytes("a:1\nb:\n");
306+
.stdout_only_bytes("a:b\n");
296307
}
297308

298309
#[test]

0 commit comments

Comments
 (0)