diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index c2e01f508e..6dd5483c6c 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -157,6 +157,8 @@ retval subdir val vals +inval +nofield # * clippy uninlined diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 25bb733301..421b35eace 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -131,8 +131,9 @@ fn cut_fields_explicit_out_delim( if delim_search.peek().is_none() { if !only_delimited { + // Always write the entire line, even if it doesn't end with `newline_char` out.write_all(line)?; - if line[line.len() - 1] != newline_char { + if line.is_empty() || line[line.len() - 1] != newline_char { out.write_all(&[newline_char])?; } } @@ -213,8 +214,12 @@ fn cut_fields_implicit_out_delim( let mut print_delim = false; if delim_search.peek().is_none() { - if !only_delimited && line[line.len() - 1] == newline_char { + if !only_delimited { + // Always write the entire line, even if it doesn't end with `newline_char` out.write_all(line)?; + if line.is_empty() || line[line.len() - 1] != newline_char { + out.write_all(&[newline_char])?; + } } return Ok(true); diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 6b376b0ca0..7d6009a30e 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -2,6 +2,9 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. + +// spell-checker:ignore defg + use crate::common::util::TestScenario; static INPUT: &str = "lists.txt"; @@ -288,7 +291,7 @@ fn test_newline_delimited() { .args(&["-f", "1", "-d", "\n"]) .pipe_in("a:1\nb:") .succeeds() - .stdout_only_bytes("a:1\n"); + .stdout_only_bytes("a:1\nb:\n"); } #[test] @@ -329,3 +332,31 @@ fn test_8bit_non_utf8_delimiter() { .succeeds() .stdout_check(|out| out == "b_c\n".as_bytes()); } + +#[test] +fn test_newline_preservation_with_f1_option() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write("1", "a\nb"); + let expected = "a\nb\n"; + ucmd.args(&["-f1-", "1"]).succeeds().stdout_is(expected); +} + +#[ignore = "Not yet implemented"] +#[test] +fn test_output_delimiter_with_character_ranges() { + new_ucmd!() + .args(&["-c2-3,4-", "--output-delim=:"]) + .pipe_in("abcdefg\n") + .succeeds() + .stdout_only("bc:defg\n"); +} + +#[ignore = "Not yet implemented"] +#[test] +fn test_output_delimiter_with_adjacent_ranges() { + new_ucmd!() + .args(&["-b1-2,3-4", "--output-d=:"]) + .pipe_in("abcd\n") + .succeeds() + .stdout_only("ab:cd\n"); +} diff --git a/util/gnu-patches/tests_cut_error_msg.patch b/util/gnu-patches/tests_cut_error_msg.patch new file mode 100644 index 0000000000..3f57d20481 --- /dev/null +++ b/util/gnu-patches/tests_cut_error_msg.patch @@ -0,0 +1,72 @@ +diff --git a/tests/cut/cut.pl b/tests/cut/cut.pl +index 1670db02e..ed633792a 100755 +--- a/tests/cut/cut.pl ++++ b/tests/cut/cut.pl +@@ -29,13 +29,15 @@ my $mb_locale = $ENV{LOCALE_FR_UTF8}; + + my $prog = 'cut'; + my $try = "Try '$prog --help' for more information.\n"; +-my $from_field1 = "$prog: fields are numbered from 1\n$try"; +-my $from_pos1 = "$prog: byte/character positions are numbered from 1\n$try"; +-my $inval_fld = "$prog: invalid field range\n$try"; +-my $inval_pos = "$prog: invalid byte or character range\n$try"; +-my $no_endpoint = "$prog: invalid range with no endpoint: -\n$try"; +-my $nofield = "$prog: an input delimiter may be specified only when " . +- "operating on fields\n$try"; ++my $from_field1 = "$prog: range '' was invalid: failed to parse range\n"; ++my $from_field_0 = "$prog: range '0' was invalid: fields and positions are numbered from 1\n"; ++my $from_field_0_dash = "$prog: range '0-' was invalid: fields and positions are numbered from 1\n"; ++my $from_field_0_2 = "$prog: range '0-2' was invalid: fields and positions are numbered from 1\n"; ++my $from_pos1 = "$prog: range '' was invalid: failed to parse range\n"; ++my $inval_fld = "$prog: range '--' was invalid: failed to parse range\n"; ++my $inval_pos = "$prog: range '--' was invalid: failed to parse range\n"; ++my $no_endpoint = "$prog: range '-' was invalid: invalid range with no endpoint\n"; ++my $nofield = "$prog: invalid input: The '--delimiter' ('-d') option only usable if printing a sequence of fields\n"; + + my @Tests = + ( +@@ -44,16 +46,16 @@ my @Tests = + + # This failed (as it should) even before coreutils-6.9.90, + # but cut from 6.9.90 produces a more useful diagnostic. +- ['zero-1', '-b0', {ERR=>$from_pos1}, {EXIT => 1} ], ++ ['zero-1', '-b0', {ERR=>$from_field_0}, {EXIT => 1} ], + + # Up to coreutils-6.9, specifying a range of 0-2 was not an error. + # It was treated just like "-2". +- ['zero-2', '-f0-2', {ERR=>$from_field1}, {EXIT => 1} ], ++ ['zero-2', '-f0-2', {ERR=>$from_field_0_2}, {EXIT => 1} ], + + # Up to coreutils-8.20, specifying a range of 0- was not an error. +- ['zero-3b', '-b0-', {ERR=>$from_pos1}, {EXIT => 1} ], +- ['zero-3c', '-c0-', {ERR=>$from_pos1}, {EXIT => 1} ], +- ['zero-3f', '-f0-', {ERR=>$from_field1}, {EXIT => 1} ], ++ ['zero-3b', '-b0-', {ERR=>$from_field_0_dash}, {EXIT => 1} ], ++ ['zero-3c', '-c0-', {ERR=>$from_field_0_dash}, {EXIT => 1} ], ++ ['zero-3f', '-f0-', {ERR=>$from_field_0_dash}, {EXIT => 1} ], + + ['1', '-d:', '-f1,3-', {IN=>"a:b:c\n"}, {OUT=>"a:c\n"}], + ['2', '-d:', '-f1,3-', {IN=>"a:b:c\n"}, {OUT=>"a:c\n"}], +@@ -96,11 +98,10 @@ my @Tests = + # Errors + # -s may be used only with -f + ['y', qw(-s -b4), {IN=>":\n"}, {OUT=>""}, {EXIT=>1}, +- {ERR=>"$prog: suppressing non-delimited lines makes sense\n" +- . "\tonly when operating on fields\n$try"}], ++ {ERR=>"$prog: invalid input: The '--only-delimited' ('-s') option only usable if printing a sequence of fields\n"}], + # You must specify bytes or fields (or chars) + ['z', '', {IN=>":\n"}, {OUT=>""}, {EXIT=>1}, +- {ERR=>"$prog: you must specify a list of bytes, characters, or fields\n$try"} ++ {ERR=>"$prog: invalid usage: expects one of --fields (-f), --chars (-c) or --bytes (-b)\n"} + ], + # Empty field list + ['empty-fl', qw(-f ''), {IN=>":\n"}, {OUT=>""}, {EXIT=>1}, +@@ -199,7 +200,7 @@ my @Tests = + + # None of the following invalid ranges provoked an error up to coreutils-6.9. + ['inval1', qw(-f 2-0), {IN=>''}, {OUT=>''}, {EXIT=>1}, +- {ERR=>"$prog: invalid decreasing range\n$try"}], ++ {ERR=>"$prog: range '2-0' was invalid: fields and positions are numbered from 1\n"}], + ['inval2', qw(-f -), {IN=>''}, {OUT=>''}, {EXIT=>1}, {ERR=>$no_endpoint}], + ['inval3', '-f', '4,-', {IN=>''}, {OUT=>''}, {EXIT=>1}, {ERR=>$no_endpoint}], + ['inval4', '-f', '1-2,-', {IN=>''}, {OUT=>''}, {EXIT=>1},