Records, Fields, and Custom Separators in awk
Control input and output field separators to slice CSV, TSV, and log lines into clean columns.
Records, Fields, and Custom Separators in awk is a free DevOps Bootcamp lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the DevOps Bootcamp learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Are Records and Fields in awk?
awk reads input line by line. Each line is called a record, and each whitespace-separated chunk within that line is called a field.
$0— the entire current record (the full line)$1— the first field$2— the second field$NF— the last field (NF= number of fields)
By default, awk splits fields on any run of whitespace (spaces or tabs). This makes it immediately useful for log parsing, command output, and space-delimited data.
#!/usr/bin/env bash
# Show how awk sees records and fields
echo 'alice 30 engineer' | awk '{ print "Name:", $1, "Age:", $2, "Role:", $3 }'
# $NF is always the last field — regardless of how many there are
echo 'one two three four five' | awk '{ print "Last field:", $NF }'The Input Field Separator: FS
The built-in variable FS (Field Separator) tells awk how to split each record into fields. Its default value is a single space, which triggers whitespace-splitting mode.
You can set FS in two ways:
- With the
-Fflag on the command line:awk -F':' - Inside a
BEGINblock:BEGIN { FS = ":" }
Both are equivalent. The BEGIN block approach is preferred in longer scripts because it keeps configuration inside the script itself, making it more readable and portable.
#!/usr/bin/env bash
# Parse /etc/passwd using ':' as the field separator
# Field 1 = username, field 3 = UID, field 7 = shell
echo 'root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin' \
| awk -F':' '{ print $1, "UID="$3, "shell="$7 }'Setting FS in the BEGIN Block
For scripts longer than a one-liner, placing FS inside a BEGIN block is the standard practice. The BEGIN block runs before awk reads any input, so the separator is ready for the first record.
This pattern also allows you to set multiple variables at once, add comments, and keep your logic self-contained in a script file.
Common separators you will encounter in the wild:
- Colon
:—/etc/passwd,/etc/shadow - Tab
\t— TSV exports, compiler output - Comma
,— CSV files (simple, no quoted fields) - Pipe
|— some log formats, database dumps
#!/usr/bin/env bash
# awk script using BEGIN to set FS for TSV input
printf 'alice\t30\tengineer\nbob\t25\tdesigner\ncarol\t35\tmanager\n' \
| awk 'BEGIN { FS = "\t" } { printf "%-10s age=%-3s role=%s\n", $1, $2, $3 }'Parsing CSV Files with awk
CSV (comma-separated values) is one of the most common data formats in shell engineering. Setting FS="," lets awk treat commas as delimiters.
Important caveat: awk's built-in CSV handling does not account for quoted fields that contain commas (e.g., "Smith, John"). For simple CSVs without quoted commas this works perfectly. For RFC 4180-compliant CSVs with quoted fields, use a proper CSV parser or miller/csvkit.
A common real-world task is extracting specific columns and filtering rows by value — both trivial with awk once FS is set.
#!/usr/bin/env bash
# Simple CSV: extract name and salary columns, filter salary > 50000
data='name,department,salary
alice,engineering,95000
bob,marketing,48000
carol,engineering,112000
dave,hr,45000'
echo "$data" | awk -F',' '
NR == 1 { next } # skip header row
$3 > 50000 { print $1, "earns", $3 }
'Multi-Character and Regex Field Separators
awk's FS is not limited to a single character — it can be a regular expression. This is powerful for real-world log formats where fields are separated by variable-length delimiters.
Examples of regex separators:
FS = "[,;]"— split on either comma or semicolonFS = "[ \t]+"— split on one or more spaces/tabs (same as default but explicit)FS = "::"— split on a literal double colonFS = "\\|"— split on a pipe character (must be escaped)
When FS is a regex, awk (gawk) treats it as a pattern, not a literal string.
#!/usr/bin/env bash
# Log lines with mixed delimiters: split on " | " or "::" or ","
# Here we split on one or more spaces or pipe characters
printf '2024-01-15 | ERROR | disk full | /dev/sda1\n2024-01-15 | INFO | startup ok | main\n' \
| awk -F' \\| ' '{ printf "date=%-12s level=%-6s msg=%s\n", $1, $2, $3 }'
# Split on multiple possible delimiters using character class
echo 'alpha,beta;gamma,delta;epsilon' \
| awk -F'[,;]' '{ for(i=1; i<=NF; i++) print i": "$i }'The Output Field Separator: OFS
OFS (Output Field Separator) controls what awk places between fields when you reconstruct a record with print. Its default is a single space.
The key insight: OFS is only inserted between fields when you use the comma syntax in print or when you assign to a field and awk rebuilds $0. If you concatenate with spaces in the source code, OFS is not used.
print $1, $2, $3— commas trigger OFS between fieldsprint $1 " " $2— explicit space, OFS ignored
A common pattern: read CSV, transform, write TSV by setting FS="," and OFS="\t".
#!/usr/bin/env bash
# Convert CSV to TSV using FS and OFS
data='alice,30,engineer
bob,25,designer
carol,35,manager'
echo "$data" | awk 'BEGIN { FS=","; OFS="\t" } { print $1, $2, $3 }'
echo '---'
# OFS also applies when you modify a field — awk rebuilds $0 with OFS
echo 'alice,30,engineer' | awk 'BEGIN { FS=","; OFS=" | " } { $1=$1; print $0 }'Forcing awk to Rebuild $0 with OFS
There is a subtle but important trick: assigning to any field (even assigning a field to itself, $1=$1) forces awk to rebuild $0 by joining all fields with OFS.
This is the idiomatic way to reformat a record's delimiters without manually printing every field:
- Set
FSto the input delimiter - Set
OFSto the desired output delimiter - Trigger a rebuild with
$1=$1 - Print
$0
This approach scales to any number of fields and avoids hard-coding $1, $2, $3, ....
#!/usr/bin/env bash
# Reformat a pipe-delimited file to comma-separated WITHOUT listing every field
printf 'alice|30|engineer|london\nbob|25|designer|paris\ncarol|35|manager|berlin\n' \
| awk 'BEGIN { FS="|"; OFS="," } { $1=$1; print $0 }'
# Useful when you don't know how many fields there are:
printf 'a|b|c|d|e|f|g\n1|2|3|4|5|6|7\n' \
| awk 'BEGIN { FS="|"; OFS="\t" } { $1=$1; print $0 }'The Record Separator: RS
Just as FS splits fields within a record, RS (Record Separator) defines what separates records from each other. By default RS is a newline, so awk processes one line at a time.
Changing RS unlocks powerful multi-line record processing:
RS=""— paragraph mode: blank lines separate records (fields can span multiple lines)RS=";"— split on semicolons (useful for SQL dumps)RS="\n"— explicit newline (the default)
In paragraph mode (RS=""), FS is still used to split fields within the multi-line record, and newlines within a record are also treated as field separators automatically.
#!/usr/bin/env bash
# Paragraph mode: each blank-line-separated block is one record
# Useful for processing structured text blocks (e.g., stanzas, config sections)
data='Name: Alice
Role: Engineer
City: London
Name: Bob
Role: Designer
City: Paris'
echo "$data" | awk 'BEGIN { RS=""; FS="\n" } {
print "Record", NR":"
for (i=1; i<=NF; i++) print " ", $i
print ""
}'The Output Record Separator: ORS
ORS (Output Record Separator) is printed after each print statement. By default it is a newline (\n), which is why every print goes to a new line.
Changing ORS lets you:
- Join records onto a single line:
ORS=" " - Add blank lines between output records:
ORS="\n\n" - Create custom output formats like JSON or XML fragments
Note that printf does not use ORS — it only applies to the bare print statement. Use printf when you need full control over formatting.
#!/usr/bin/env bash
# Join all matching lines onto one line by setting ORS to a space
printf 'apple\nbanana\ncherry\ndate\n' | awk '{ ORS=" " } { print $0 }'
echo # final newline
# Add a blank line after every output record for readability
printf 'alice 30 engineer\nbob 25 designer\ncarol 35 manager\n' \
| awk 'BEGIN { ORS="\n\n" } { print $1, "is a", $3 }'Practical Example: Parsing Apache Access Logs
Apache/nginx access logs have a well-known format with space-delimited fields. Some fields (like the timestamp) contain spaces and are wrapped in brackets or quotes — making direct awk field-splitting tricky.
A practical approach is to use a regex FS that accounts for the structure, or to extract specific fields by position knowing the exact format.
The Combined Log Format fields are roughly:
- Client IP
- Ident (usually
-) - Auth user (usually
-) - Timestamp (in brackets, counts as one token)
- Request method+path+protocol (in quotes)
- HTTP status code
- Response bytes
#!/usr/bin/env bash
# Parse a simulated Apache Combined Log Format line
# Extract: IP, status code, bytes, and request path
logs='192.168.1.10 - alice [15/Jan/2024:10:23:45 +0000] "GET /api/users HTTP/1.1" 200 1523
10.0.0.5 - - [15/Jan/2024:10:23:46 +0000] "POST /login HTTP/1.1" 401 89
172.16.0.3 - bob [15/Jan/2024:10:23:47 +0000] "GET /dashboard HTTP/1.1" 200 8741'
echo "$logs" | awk '{
ip = $1
status = $9
bytes = $10
# Extract the request path from the quoted string in field 7
split($7, parts, "/")
path = "/" parts[2]
printf "ip=%-15s status=%s bytes=%-6s endpoint=%s\n", ip, status, bytes, path
}'Combining FS, OFS, RS, and ORS in a Pipeline
In real shell engineering you rarely use these separators in isolation. A common pipeline pattern is to use awk as a format converter in the middle of a pipeline, transforming one structured format into another.
Key takeaways for combining separators:
- Always set
FSandOFStogether inBEGINwhen converting formats - Use
$1=$1to trigger a rebuild of$0when you want all fields reformatted without listing them - Change
RSonly when your records genuinely span multiple lines - Use
ORSto control spacing between output records - Prefer
printffor precise formatting; useprintwhenORSauto-termination is convenient
#!/usr/bin/env bash
# Full pipeline: read a colon-delimited file,
# filter rows where field 3 (UID) >= 1000 (real users),
# then output as tab-separated: username, UID, home dir
echo 'root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
alice:x:1001:1001:Alice:/home/alice:/bin/bash
bob:x:1002:1002:Bob:/home/bob:/bin/zsh
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin' \
| awk 'BEGIN { FS=":"; OFS="\t" } $3 >= 1000 { print $1, $3, $6 }'Knowledge Check: OFS and Field Rebuilding
Test your understanding of how OFS interacts with field assignment in awk.
Lesson Recap: Records, Fields, and Separators
You now have full control over how awk reads and writes structured data. Here is a summary of the four separator variables:
FS— Input Field Separator. Set with-For inBEGIN. Can be a character, string, or regex. Default: whitespace.OFS— Output Field Separator. Inserted between fields inprint $1, $2calls and when awk rebuilds$0after a field assignment. Default: single space.RS— Input Record Separator. Defines what separates records (lines). Default: newline. Set to empty string for paragraph mode.ORS— Output Record Separator. Appended after everyprint. Default: newline. Change to join lines or add spacing.
The most important pattern to remember: set both FS and OFS in BEGIN, then use $1=$1 to rebuild $0 whenever you want to reformat delimiters across all fields without hard-coding field positions. This pattern works for files with any number of columns and is the foundation of awk-based format conversion pipelines.
Frequently asked questions
Is the “Records, Fields, and Custom Separators in awk” lesson free?
Yes — the full text of “Records, Fields, and Custom Separators in awk” is free to read here on the web, and the DevOps Bootcamp course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DevOps Bootcamp course, upgrade to CoddyKit PRO.
What will I learn in “Records, Fields, and Custom Separators in awk”?
Control input and output field separators to slice CSV, TSV, and log lines into clean columns. You practise DevOps Bootcamp with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start DevOps Bootcamp?
No prior experience is required. DevOps Bootcamp on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Records, Fields, and Custom Separators in awk” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this DevOps Bootcamp lesson?
Yes. Every DevOps Bootcamp lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Records, Fields, and Custom Separators in awk
- Patterns, Ranges, and BEGIN/END Blocks
- Aggregation with awk Arrays and Grouping
- awk Functions, printf Formatting, and Report Generation