mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
split: correct filename creation algorithm
Fix two issues with the filename creation algorithm. First, this
corrects the behavior of the `-a` option. This commit ensures a
failure occurs when the number of chunks exceeds the number of
filenames representable with the specified fixed width:
$ printf "%0.sa" {1..11} | split -d -b 1 -a 1
split: output file suffixes exhausted
Second, this corrects the behavior of the default behavior when `-a`
is not specified on the command line. Previously, it was always
settings the filenames to have length 2 suffixes. This commit corrects
the behavior to follow the algorithm implied by GNU split, where the
filename lengths grow dynamically by two characters once the number of
chunks grows sufficiently large:
$ printf "%0.sa" {1..91} | ./target/debug/coreutils split -d -b 1 \
> && ls x* | tail
x81
x82
x83
x84
x85
x86
x87
x88
x89
x9000
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+16
-46
@@ -7,15 +7,17 @@
|
||||
|
||||
// spell-checker:ignore (ToDO) PREFIXaa
|
||||
|
||||
mod filenames;
|
||||
mod platform;
|
||||
|
||||
use crate::filenames::FilenameFactory;
|
||||
use clap::{crate_version, App, Arg, ArgMatches};
|
||||
use std::convert::TryFrom;
|
||||
use std::env;
|
||||
use std::fs::remove_file;
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write};
|
||||
use std::path::Path;
|
||||
use std::{char, fs::remove_file};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
||||
use uucore::parse_size::parse_size;
|
||||
@@ -27,7 +29,7 @@ static OPT_ADDITIONAL_SUFFIX: &str = "additional-suffix";
|
||||
static OPT_FILTER: &str = "filter";
|
||||
static OPT_NUMERIC_SUFFIXES: &str = "numeric-suffixes";
|
||||
static OPT_SUFFIX_LENGTH: &str = "suffix-length";
|
||||
static OPT_DEFAULT_SUFFIX_LENGTH: &str = "2";
|
||||
static OPT_DEFAULT_SUFFIX_LENGTH: &str = "0";
|
||||
static OPT_VERBOSE: &str = "verbose";
|
||||
|
||||
static ARG_INPUT: &str = "input";
|
||||
@@ -98,7 +100,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
split(&settings)
|
||||
split(settings)
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
@@ -340,39 +342,7 @@ impl Splitter for ByteSplitter {
|
||||
}
|
||||
}
|
||||
|
||||
// (1, 3) -> "aab"
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn str_prefix(i: usize, width: usize) -> String {
|
||||
let mut c = "".to_owned();
|
||||
let mut n = i;
|
||||
let mut w = width;
|
||||
while w > 0 {
|
||||
w -= 1;
|
||||
let div = 26usize.pow(w as u32);
|
||||
let r = n / div;
|
||||
n -= r * div;
|
||||
c.push(char::from_u32((r as u32) + 97).unwrap());
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
// (1, 3) -> "001"
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn num_prefix(i: usize, width: usize) -> String {
|
||||
let mut c = "".to_owned();
|
||||
let mut n = i;
|
||||
let mut w = width;
|
||||
while w > 0 {
|
||||
w -= 1;
|
||||
let div = 10usize.pow(w as u32);
|
||||
let r = n / div;
|
||||
n -= r * div;
|
||||
c.push(char::from_digit(r as u32, 10).unwrap());
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
fn split(settings: &Settings) -> UResult<()> {
|
||||
fn split(settings: Settings) -> UResult<()> {
|
||||
let mut reader = BufReader::new(if settings.input == "-" {
|
||||
Box::new(stdin()) as Box<dyn Read>
|
||||
} else {
|
||||
@@ -392,19 +362,19 @@ fn split(settings: &Settings) -> UResult<()> {
|
||||
}
|
||||
};
|
||||
|
||||
// This object is responsible for creating the filename for each chunk.
|
||||
let filename_factory = FilenameFactory::new(
|
||||
settings.prefix,
|
||||
settings.additional_suffix,
|
||||
settings.suffix_length,
|
||||
settings.numeric_suffix,
|
||||
);
|
||||
let mut fileno = 0;
|
||||
loop {
|
||||
// Get a new part file set up, and construct `writer` for it.
|
||||
let mut filename = settings.prefix.clone();
|
||||
filename.push_str(
|
||||
if settings.numeric_suffix {
|
||||
num_prefix(fileno, settings.suffix_length)
|
||||
} else {
|
||||
str_prefix(fileno, settings.suffix_length)
|
||||
}
|
||||
.as_ref(),
|
||||
);
|
||||
filename.push_str(settings.additional_suffix.as_ref());
|
||||
let filename = filename_factory
|
||||
.make(fileno)
|
||||
.ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?;
|
||||
let mut writer = platform::instantiate_current_writer(&settings.filter, filename.as_str());
|
||||
|
||||
let bytes_consumed = splitter
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes asciilowercase
|
||||
extern crate rand;
|
||||
extern crate regex;
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
fs::{read_dir, File},
|
||||
io::BufWriter,
|
||||
io::{BufWriter, Read},
|
||||
};
|
||||
|
||||
fn random_chars(n: usize) -> String {
|
||||
@@ -340,3 +340,71 @@ fn test_split_invalid_bytes_size() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn file_read(at: &AtPath, filename: &str) -> String {
|
||||
let mut s = String::new();
|
||||
at.open(filename).read_to_string(&mut s).unwrap();
|
||||
s
|
||||
}
|
||||
|
||||
// TODO Use char::from_digit() in Rust v1.51.0 or later.
|
||||
fn char_from_digit(n: usize) -> char {
|
||||
(b'a' + n as u8) as char
|
||||
}
|
||||
|
||||
/// Test for the default suffix length behavior: dynamically increasing size.
|
||||
#[test]
|
||||
fn test_alphabetic_dynamic_suffix_length() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
// Split into chunks of one byte each.
|
||||
//
|
||||
// The input file has (26^2) - 26 + 1 = 651 bytes. This is just
|
||||
// enough to force `split` to dynamically increase the length of
|
||||
// the filename for the very last chunk.
|
||||
//
|
||||
// We expect the output files to be named
|
||||
//
|
||||
// xaa, xab, xac, ..., xyx, xyy, xyz, xzaaa
|
||||
//
|
||||
ucmd.args(&["-b", "1", "sixhundredfiftyonebytes.txt"])
|
||||
.succeeds();
|
||||
for i in 0..25 {
|
||||
for j in 0..26 {
|
||||
let filename = format!("x{}{}", char_from_digit(i), char_from_digit(j),);
|
||||
let contents = file_read(&at, &filename);
|
||||
assert_eq!(contents, "a");
|
||||
}
|
||||
}
|
||||
assert_eq!(file_read(&at, "xzaaa"), "a");
|
||||
}
|
||||
|
||||
/// Test for the default suffix length behavior: dynamically increasing size.
|
||||
#[test]
|
||||
fn test_numeric_dynamic_suffix_length() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
// Split into chunks of one byte each, use numbers instead of
|
||||
// letters as file suffixes.
|
||||
//
|
||||
// The input file has (10^2) - 10 + 1 = 91 bytes. This is just
|
||||
// enough to force `split` to dynamically increase the length of
|
||||
// the filename for the very last chunk.
|
||||
//
|
||||
// x00, x01, x02, ..., x87, x88, x89, x9000
|
||||
//
|
||||
ucmd.args(&["-d", "-b", "1", "ninetyonebytes.txt"])
|
||||
.succeeds();
|
||||
for i in 0..90 {
|
||||
let filename = format!("x{:02}", i);
|
||||
let contents = file_read(&at, &filename);
|
||||
assert_eq!(contents, "a");
|
||||
}
|
||||
assert_eq!(file_read(&at, "x9000"), "a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_suffixes_exhausted() {
|
||||
new_ucmd!()
|
||||
.args(&["-b", "1", "-a", "1", "asciilowercase.txt"])
|
||||
.fails()
|
||||
.stderr_only("split: output file suffixes exhausted");
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
abcdefghijklmnopqrstuvwxyz
|
||||
+1
@@ -0,0 +1 @@
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
Reference in New Issue
Block a user