I first needed split when I had to email a 4GB database dump to a colleague and the mail server had a 25MB attachment limit. Instead of messing around with a compression tool that still wouldn’t get me under the limit, I split the file into 20MB chunks, sent them one at a time, and had my colleague reassemble them with cat. It felt almost too simple for how well it worked, and I’ve used split for that exact class of problem — breaking large files down for transfer, upload limits, or parallel processing — ever since.
What split Does
split breaks a single file into multiple smaller pieces, either by a fixed number of lines, a fixed byte size, or a target number of output chunks. The pieces can be reassembled later with cat.
split [OPTION]... [FILE [PREFIX]]
If no file is given, split reads from standard input. The default output prefix is x, and by default it splits into files of 1000 lines each, using alphabetic suffixes (xaa, xab, xac, …).
I tested the default behavior:
$ split -l 5 nums.txt part_
$ ls part_*
part_aa part_ab part_ac part_ad
A 20-line file split into 5-line chunks produced four files, each named with a sequential alphabetic suffix.
Core Options and Parameters
-l NUMBER — Split by Number of Lines
split -l 1000 bigfile.txt chunk_
This is the most common mode — split by line count, useful for CSVs, logs, or any line-oriented text data where you don’t want to split a record in the middle.
-b SIZE — Split by Byte Size
split -b 20 nums.txt chunk_
I tested this against a small file and it produced byte-exact chunks, cutting wherever the byte count landed — including potentially mid-line, since -b doesn’t respect line boundaries:
$ split -b 20 -d nums.txt chunk_
$ cat chunk_00
1
2
3
4
5
6
7
8
9
10
SIZE accepts unit suffixes: K, M, G, T (powers of 1024) or KB, MB, GB (powers of 1000). So split -b 500M largefile.iso part_ splits into 500-megabyte chunks.
-C SIZE — Split by Byte Size Without Breaking Lines
This is the option I actually reach for more often than plain -b when working with text files, because it respects line boundaries while still targeting a byte-size ceiling per chunk:
split -C 10M --numeric-suffixes access.log chunk_
Each output file will be at most 10MB, but split won’t cut a line in half to hit that number — it’ll end the chunk at the last complete line before the limit.
-n CHUNKS — Split Into a Specific Number of Files
$ split -n 4 nums.txt piece_
$ ls piece_*
piece_aa piece_ab piece_ac piece_ad
This tells split “I want exactly 4 output files” and it figures out the size per chunk based on the total input size, rather than you specifying the size per chunk yourself. -n supports several sub-modes documented in split --help:
N— split into N files based on size of input (may split lines)l/N— split into N files without splitting linesK/N— output only the Kth of N chunks to stdoutr/N— round-robin distribution of lines across N files
For example, split -n l/4 data.csv part_ guarantees 4 files without ever cutting a line in half, redistributing byte boundaries as needed to keep lines intact.
-d — Numeric Suffixes Instead of Alphabetic
By default, suffixes are alphabetic (aa, ab, ac…). With -d, you get numeric suffixes starting at 00:
$ split -l 5 -d nums.txt seg_
$ ls seg_*
seg_00 seg_01 seg_02 seg_03
I generally prefer -d for scripting because numeric ordering sorts more intuitively and is easier to iterate over programmatically (seg_00 through seg_99 rather than remembering alphabetic sequencing rules once you pass az).
-x — Hexadecimal Suffixes
Less commonly used, but available for cases needing hex-based naming:
split -l 5 -x nums.txt seg_
-a LENGTH — Suffix Length
Controls how many characters are used in the generated suffix (default is 2, giving up to 676 alphabetic combinations or 100 numeric combinations):
split -l 100 -d -a 4 hugefile.txt part_
With -a 4 and -d, you get up to 10,000 numbered output files (part_0000 through part_9999) — necessary when splitting extremely large files into many small pieces.
--additional-suffix=SUFFIX — Append a File Extension
$ split --additional-suffix=.txt -d -l 5 nums.txt seg_
$ ls seg_*
seg_00.txt seg_01.txt seg_02.txt seg_03.txt
I use this constantly when the split pieces need to retain a recognizable file extension — for instance, splitting a huge .sql dump into pieces that still end in .sql so tooling that inspects file extensions still works correctly.
-e — Elide Empty Output Files
When using -n and the input doesn’t divide evenly, this prevents split from generating empty trailing files.
--filter=COMMAND — Pipe Each Chunk Through a Command
Instead of writing plain files, you can pipe each chunk through an arbitrary shell command, with $FILE representing the intended output filename:
split -l 10000 --filter='gzip > $FILE.gz' access.log part_
This compresses each chunk on the fly as it’s created, which is a huge time-saver when splitting and compressing a large file would otherwise require two separate passes.
How split Works Internally
split reads the input sequentially (it doesn’t need random access, so it works fine on pipes too) and writes to a new output file each time the current chunk’s line count, byte count, or target size is reached. For -l and -C, it scans for newline characters to determine safe cut points. For -n, split needs to know the total input size upfront to calculate even divisions — this generally requires the input to be a seekable regular file rather than a pipe, since split needs to stat() the file to get its size before deciding how to slice it.
Practical, Real-World Examples
1. Splitting a Large File for Email Attachment Limits
split -b 20M largefile.zip part_
Reassemble on the other end with:
cat part_* > largefile.zip
2. Splitting a CSV for Parallel Processing
split -n l/8 --numeric-suffixes=1 -a 2 --additional-suffix=.csv huge_data.csv chunk_
for f in chunk_*.csv; do
process_data.sh "$f" &
done
wait
This splits into 8 roughly equal chunks without breaking any CSV row, then processes them in parallel across CPU cores — a pattern I’ve used to cut multi-hour ETL jobs down significantly on multi-core servers.
3. Splitting Log Files by Size Before Archiving
split -C 100M --numeric-suffixes --additional-suffix=.log access.log archive_part_
gzip archive_part_*.log
4. Splitting and Compressing in One Pass
split -l 500000 --filter='gzip > $FILE.gz' -d huge_export.csv export_part_
5. Reassembling Split Files
Regardless of how the file was split (by lines, bytes, or chunk count), reassembly is always the same as long as pieces are concatenated in the correct order:
cat part_aa part_ab part_ac > original_file
# or, relying on shell glob ordering:
cat part_* > original_file
I always double-check that shell glob expansion sorts the pieces in the correct order — alphabetic suffixes sort correctly up to 2 characters, but if you used -a 3 or more with a mix of numeric widths, verify with ls part_* first before trusting cat part_* blindly.
split in Shell Scripting and Automation
A pattern I use for distributing work across worker nodes:
#!/bin/bash
INPUT="urls_to_process.txt"
WORKERS=4
split -n l/$WORKERS -d --additional-suffix=.txt "$INPUT" worker_
for f in worker_*.txt; do
ssh "worker-$(basename "$f" .txt)" "process_urls.sh" < "$f" &
done
wait
echo "All workers finished"
This splits a URL list into N even pieces and dispatches each to a different worker over SSH, running them concurrently — a lightweight alternative to setting up a full job queue for simple parallel batch work.
Comparing split to Related Commands
| Task | Best Tool |
|---|---|
| Splitting a file into pieces | split |
| Splitting on a specific delimiter pattern (e.g., by section headers) | csplit |
| Extracting a range of lines | sed -n or head/tail |
| Combining split pieces back | cat |
| Compressing large files instead of splitting | gzip/xz/zstd |
split and csplit are often confused. split divides purely by size or count with no awareness of content. csplit divides based on patterns or line numbers you specify (e.g., “start a new file every time you see a line matching ^Chapter“), which is the right tool when you need content-aware splitting rather than purely mechanical splitting.
Troubleshooting Common split Issues
Reassembled file doesn’t match the original — verify the pieces were concatenated in the correct order; use md5sum original_file and cat part_* | md5sum to compare checksums.
-n fails with “cannot determine file size” on a pipe — -n (except the r/N round-robin mode) requires a seekable regular file. Write the piped data to a temp file first, then run split -n against that file.
Too many output files hit -a suffix length limits — increase -a to allow more unique suffixes, e.g., -a 4 for up to 10,000 numeric files.
Lines split in half unexpectedly — you used -b (pure byte split) instead of -C (byte split that respects line boundaries). Switch to -C if line integrity matters.
Performance Optimization
split streams input rather than loading the entire file into memory, so it scales well to very large files without excessive RAM usage. For maximum throughput on very large files, -b/-C with a large chunk size (reducing the total number of open()/close() syscalls) is generally faster than a very fine-grained split into thousands of tiny files, since file creation overhead adds up.
Security Implications
Be cautious with --filter, since it passes chunk data through an arbitrary shell command — never use --filter with a command string built from untrusted input, as this creates a command injection risk identical to any other unsanitized shell interpolation.
Compatibility Across Distributions
split is part of GNU coreutils and is available by default on Ubuntu, Debian, Fedora, RHEL/CentOS, Arch, and openSUSE (tested here at coreutils 9.4). BSD/macOS ship a similar but more limited split lacking some GNU extensions like -C, --filter, and --additional-suffix — check split --version (GNU) versus BSD-style man split if writing cross-platform scripts.
split and Disk I/O Behavior
It’s worth understanding what’s actually happening at the system level when split processes a very large file, since this explains both why it’s efficient and where its practical limits lie. split opens the source file once and reads it sequentially in reasonably sized buffered chunks, writing each output file as it goes and closing it once the target line count, byte count, or size threshold is reached. Because it never needs random access into the source file for -l, -b, or -C modes, it can operate on non-seekable input (a pipe) just as well as on a regular file for those modes — the exception being -n, which, as covered earlier, needs to know the total input size up front to divide it evenly, and therefore requires a seekable regular file in most of its sub-modes.
On spinning-disk storage, sequential reads and writes like this are close to the best-case I/O pattern achievable, since the disk head doesn’t need to seek around. On SSDs the distinction matters less, but the sequential access pattern still tends to produce more predictable throughput than something that jumps around the file unpredictably. This is one of the reasons split remains a genuinely fast tool even on very large files — multi-gigabyte ISO images, large database dumps, and massive log archives all split at close to raw disk throughput speed, with split‘s own CPU overhead being close to negligible.
Choosing Between -b, -C, and -n in Practice
New users of split often aren’t sure which of these three sizing modes fits their situation, so it’s worth laying out a clear decision process:
- Use
-bwhen you have a hard, external byte-size constraint that doesn’t care about content structure — an upload limit, a removable media capacity limit, a fixed-size buffer somewhere downstream. Line integrity doesn’t matter because the consuming system treats the data as an opaque byte stream (e.g., a.taror.ziparchive split for transfer, where the pieces get concatenated back together before ever being interpreted as text). - Use
-Cwhen you have a similar byte-size constraint, but the content is line-oriented text that must remain valid line-by-line after splitting — CSV exports, log files, or any text format where a downstream tool will try to parse each piece independently rather than only after reassembly. - Use
-nwhen your goal isn’t a specific size at all, but rather a specific number of roughly-even pieces — most commonly for distributing work across a known number of parallel workers or CPU cores, where the exact byte size of each piece matters far less than having a predictable, small number of output files.
Verifying Split Output Integrity
For anything beyond casual use, it’s worth building a habit of verifying that reassembly actually reproduces the original file exactly, especially before deleting the original or relying on the split pieces for something important like a backup transfer:
split -b 100M important_archive.tar.gz part_
cat part_* > reassembled.tar.gz
if cmp -s important_archive.tar.gz reassembled.tar.gz; then
echo "Verified: reassembly matches original exactly"
else
echo "MISMATCH: reassembled file differs from original" >&2
fi
cmp -s performs a byte-for-byte comparison silently (only exit code, no output), making it a cheap, reliable check to fold into any script that splits a file for transfer and expects to reassemble it later on the receiving end.
Handling Split Files Across a Network Transfer
A pattern I’ve used when transferring split archives to a remote host where bandwidth or connection stability is a concern — transferring each piece individually rather than the whole file allows a failed transfer to be resumed from the last successfully transferred piece, rather than restarting the entire large file from scratch:
split -b 200M large_dataset.tar.gz chunk_
for f in chunk_*; do
rsync -avz --partial "$f" remote:/data/incoming/
done
ssh remote 'cat /data/incoming/chunk_* > /data/incoming/large_dataset.tar.gz'
Using rsync --partial on each individually split piece, rather than the whole original file, means a dropped connection only costs you the progress on the current chunk, not the entire transfer.
Summary
split turns “this file is too big for X” into a non-problem — whether X is an email attachment limit, an upload size cap, or a desire to parallelize processing across cores or machines. The options that matter most in practice are -l/-C for line-safe splitting, -n for a fixed number of output files, -d for sane numeric ordering, and --filter for compressing on the fly. Once split pieces exist, cat always brings them back together.
References
- GNU Coreutils Manual —
split: https://www.gnu.org/software/coreutils/manual/html_node/split-invocation.html man split(local manual page)