Skip to content
Linux Basics: Split Text at Section Boundaries with GNU csplit

Linux Basics: Split Text at Section Boundaries with GNU csplit

October 24, 2026

Some files have logical sections rather than equally sized chunks. A diagnostic report might contain headings; a simple export might separate records with empty lines. GNU csplit can use those boundaries to create separate files without opening an editor.

These examples were tested on Linux with GNU Bash 5.2.37 and GNU coreutils 9.7. They use small, invented text inputs, not live logs or system files. GNU-specific options may differ on other systems.

Prepare a scratch directory

You need Bash, GNU csplit, a text editor, and the cat, mktemp, rm, and rmdir utilities. Keep using the same terminal. Save each named source file in this new directory, then run it once in order. Calling bash explicitly avoids changing executable permissions. No administrator access is needed.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Keep headings with their sections

Save this as csplit-headings.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
printf '%s\n' '## CPU' 'cores=4' '## Memory' 'gib=8' > sections.txt
csplit --quiet --elide-empty-files --prefix=section- \
  --suffix-format='%02d.txt' sections.txt '/^## /' '{*}'
for file in section-00.txt section-01.txt; do
  printf '%s:\n' "$file"
  cat -- "$file"
done

Run it:

bash csplit-headings.sh

Output:

section-00.txt:
## CPU
cores=4
section-01.txt:
## Memory
gib=8

The two headings start the resulting files rather than ending the previous ones. /^## / identifies a heading at the beginning of a line, and the quoted '{*}' repeats that boundary search. --elide-empty-files avoids an empty piece before the first heading. --quiet hides byte counts so the script can display the contents instead.

The explicit prefix and two-digit suffix make the outputs easy to identify in this small exercise. The source remains unchanged. The GNU csplit manual describes its pattern syntax and output naming.

2. Remove empty separator lines

Save this as csplit-records.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
printf '%s\n' 'name=alpha' 'count=2' '' 'name=beta' 'count=5' > records.txt
csplit --quiet --elide-empty-files --suppress-matched \
  --prefix=record- --suffix-format='%02d.txt' records.txt '/^$/' '{*}'
for file in record-00.txt record-01.txt; do
  printf '%s:\n' "$file"
  cat -- "$file"
done

Run it:

bash csplit-records.sh

Output:

record-00.txt:
name=alpha
count=2
record-01.txt:
name=beta
count=5

The empty line is structural here, not part of either record. --suppress-matched leaves that separator out. This pattern matches a genuinely empty line; a line containing spaces is not empty. The input is deliberately simple, with no multiline field values.

Notice that joining these outputs would not recreate the original bytes because the separator was discarded. Keep the source when you need a faithful archive as well as convenient working pieces.

3. Discard a known preamble

Save this as csplit-preamble.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
printf '%s\n' 'generated report' 'do not parse this' \
  'DATA' 'north=8' 'south=13' > report.txt
csplit --quiet --prefix=payload- --suffix-format='%02d.txt' \
  report.txt '%^DATA$%+1'
cat -- payload-00.txt

Run it:

bash csplit-preamble.sh

Output:

north=8
south=13

Percent-delimited patterns skip a section instead of writing it. The +1 offset moves the boundary past the DATA marker, so the remaining payload starts with north=8. Unlike a fixed line count, this boundary still works if the report gains additional preamble lines before the marker.

Keep in mind

Use a new output directory for each real job. Reusing a prefix can overwrite matching files or leave stale higher-numbered pieces from an earlier run. A missing required pattern is an error; by default, csplit removes the output files it created during a failed run. That behavior does not restore older files it overwrote.

These examples rely on line boundaries and deliberately recognizable markers. Use a format-aware parser when the data format permits the marker inside a value. Do not apply the empty-line recipe blindly to structured formats with different record rules.

Clean up

Remove only the named practice files, then return to your original directory:

rm -- csplit-headings.sh csplit-records.sh csplit-preamble.sh \
  sections.txt records.txt report.txt section-00.txt section-01.txt \
  record-00.txt record-01.txt payload-00.txt
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

Acknowledgements

This post was created with assistance from AI.

Last updated on