awk Command: Printing Fields, the -F Delimiter and Conditional Patterns
The awk command is a Linux tool that reads a text file line by line, splits each line into fields on whitespace or a delimiter you specify, and prints or calculates only the fields from lines that match a condition. On an AlmaLinux 9 server, we tallied requests per client IP and counts per status code from a sample web server access log file with a single line piped into sort, and found that all three 404 responses among the twelve lines were requests for wp-login.php and .env from one IP.
The output below was produced as the root account in a working folder on an AlmaLinux 9.8 server. The access log, account list, sales records and score file are sample files made for testing, and the IP addresses and account names in them are example values.
1️⃣ Basic awk command usage
awk splits each line it reads on whitespace and numbers the fields $1, $2, $3 and so on from the left. We checked this with scores.txt, where name, result and score are separated by spaces.
cat scores.txtalice pass 82
bob fail 47
carol pass 91
dave pass 68
erin fail 55The curly braces inside the single quotes hold the action to run on each line, and listing field numbers after print, separated by commas, outputs only those fields. Printing the first and third fields leaves just the name and score.
awk '{print $1, $3}' scores.txtalice 82
bob 47
carol 91
dave 68
erin 55If you drop the comma between fields and write $1 $3, the two values run together without a space. The comma in print inserts the output separator (one space by default), and values written side by side without a comma are joined into one string.
awk '{print $1 $3}' scores.txtalice82
bob47
carol91
dave68
erin55NR is the number of the line read so far, and NF is the number of fields on that line. $NF means the last field, so you can pick the last column even when the number of fields differs from line to line.
awk '{print NR, NF, $NF}' scores.txt1 3 82
2 3 47
3 3 91
4 3 68
5 3 55A web server access log is also split by spaces, so you pick fields the same way. The field numbers for the first line of the test access log are as follows.
| Field | Value on the first line | Meaning |
|---|---|---|
| $1 | 192.0.2.10 | Client IP |
| $4 | [29/Oct/2026:00:01:12 | Request date and time |
| $6 | "GET | Request method (with a leading double quote) |
| $7 | /index.html | Request URL |
| $9 | 200 | Status code |
| $10 | 5120 | Bytes sent |
| $11 | "-" | Previous page (Referer) |
| $12~$15 | "Mozilla/5.0 (X11; Linux x86_64)" | User agent (split at each space) |
We printed $1, $7 and $9, which are the client IP, request URL and status code.
awk '{print $1, $7, $9}' access.log192.0.2.10 /index.html 200
198.51.100.7 /about.html 200
192.0.2.10 /images/logo.png 200
203.0.113.25 /wp-login.php 404
203.0.113.25 /wp-login.php 404
192.0.2.10 /contact.html 200
198.51.100.7 /old-page.html 301
203.0.113.25 /.env 404
192.0.2.44 /index.html 200
198.51.100.7 /api/search?q=awk 500
192.0.2.44 /images/logo.png 304
192.0.2.10 /index.html 200A column with spaces in its value, like the user agent, is split into several fields. NF was 15 on all of the first three lines, and $NF is the last piece of the user agent.
awk '{print NF, $NF}' access.log | head -315 x86_64)"
15 x86_64)"
15 x86_64)"2️⃣ Setting the delimiter with awk -F
For a file whose columns are separated by something other than spaces, put that character after -F. When we printed $1 without -F from the account list users.txt, which has the same form as /etc/passwd, most lines came out whole.
awk '{print $1}' users.txtroot:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
nobody:x:65534:65534:Kernel
user:x:1000:1000:Example
deploy:x:1001:1001:Deploy
backup:x:1002:1002::/home/backup:/sbin/nologinThe default delimiter is whitespace, so a line joined only by colons has just one field. Only the nobody, user and deploy lines, which have a space in the description column, are cut at that space.
Setting the colon as the delimiter with -F: separates the account name ($1) from the login shell ($7).
awk -F: '{print $1, $7}' users.txtroot /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
nobody /sbin/nologin
user /bin/bash
deploy /bin/bash
backup /sbin/nologinSplit comma-separated CSV files with -F,. Printing the item and quantity from the sales records in sales.csv also splits the header on the first line (item qty) the same way, so it appears too.
awk -F, '{print $2, $3}' sales.csvitem qty
keyboard 2
mouse 5
monitor 1
keyboard 1
cable 10
mouse 2
monitor 2-F, does not suit CSV files with commas inside double-quoted columns. In quoted.csv, which has commas in the item column, fields are also split at the commas inside the quotes, leaving only the first part of the item name in the second field.
awk -F, '{print $2}' quoted.csvitem
"cable
keyboardFor such files, the official GNU Awk manual describes using the FPAT variable. If you write the shape of one field as a regular expression instead of a delimiter, a column wrapped in quotes comes out as one field.
awk -v FPAT='([^,]*)|("[^"]*")' '{print $2}' quoted.csvitem
"cable, usb"
keyboardTo use several delimiters, write them as a regular expression inside square brackets. With both the colon and the slash as delimiters, $NF becomes the last name in the login shell path (bash, nologin).
awk -F'[:/]' '{print $1, $NF}' users.txtroot bash
bin nologin
daemon nologin
nobody nologin
user bash
deploy bash
backup nologin3️⃣ awk command condition patterns
Picking fields still prints every line, so to keep only the lines you need, put a condition (pattern) before the curly braces. A string between slashes selects only the lines that contain it, and if you omit the action, the whole line is printed.
awk '/wp-login/' access.log203.0.113.25 - - [29/Oct/2026:00:04:51 +0000] "POST /wp-login.php HTTP/1.1" 404 196 "-" "Mozilla/5.0 (X11; Linux x86_64)"
203.0.113.25 - - [29/Oct/2026:00:04:52 +0000] "POST /wp-login.php HTTP/1.1" 404 196 "-" "Mozilla/5.0 (X11; Linux x86_64)"The lines selected are the same as with the grep command, and awk can then pick fields from those lines and print them directly. Passing the same two lines selected with grep to awk through a pipe leaves only the IP and status code.
grep wp-login access.log | awk '{print $1, $9}'203.0.113.25 404
203.0.113.25 404Field values are compared with ==, !=, >, >=, <, <=. We selected only the lines whose status code ($9) is 404 and printed the IP and request URL.
awk '$9 == 404 {print $1, $7}' access.log203.0.113.25 /wp-login.php
203.0.113.25 /wp-login.php
203.0.113.25 /.envWhen comparing with a string, wrap the value in double quotes. We selected only the names whose second field is pass.
awk '$2 == "pass" {print $1}' scores.txtalice
carol
daveNumeric comparisons are for selecting by range, as with account numbers (UIDs). Selecting accounts with a UID of 1000 or higher returns the regular accounts along with nobody, whose UID is 65534.
awk -F: '$3 >= 1000 {print $1, $3}' users.txtnobody 65534
user 1000
deploy 1001
backup 1002Combine two conditions with &&. Adding a condition of below 65534 removes nobody and leaves only the three regular accounts and their home folders.
awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $6}' users.txtuser /home/user
deploy /home/deploy
backup /home/backup[~] checks whether a field matches a regular expression, and [!~] checks that it does not. Selecting accounts whose login shell ($7) ends in bash and accounts whose shell is not nologin returns the same three accounts.
awk -F: '$7 ~ /bash$/ {print $1}' users.txtroot
user
deployawk -F: '$7 !~ /nologin/ {print $1, $7}' users.txtroot /bin/bash
user /bin/bash
deploy /bin/bashAn NR condition selects by line number. NR > 1 removes the CSV header line and leaves only the sales records.
awk -F, 'NR > 1 {print $2, $3}' sales.csvkeyboard 2
mouse 5
monitor 1
keyboard 1
cable 10
mouse 2
monitor 2You can pipe the output of other commands and split it the same way. Picking the file size ($5) and name ($9) from ls -l output gave a blank first line, because the first line of ls -l, [total 24], has no fifth or ninth field.
ls -l | awk '{print $5, $9}'
1567 access.log
79 quoted.csv
207 sales.csv
85 scores-copy.txt
85 scores.txt
315 users.txtAdding NR > 1 removes the first line and leaves only the file sizes and names.
ls -l | awk 'NR > 1 {print $5, $9}'1567 access.log
79 quoted.csv
207 sales.csv
85 scores-copy.txt
85 scores.txt
315 users.txt4️⃣ awk BEGIN, END, sums and averages
A BEGIN block runs once before the first line is read, and an END block runs once after the last line is processed. For a sum, add to a variable on each line and print it in END. The diagram below shows the order awk goes through on each line when totaling the amounts in the sales records.
$ awk -F, 'NR > 1 {t += $3 * $4} END {printf "total %.2f\n", t}' sales.csvAdding up all the bytes sent ($10) in the access log gave 41298 bytes. A variable used for the first time starts empty (0 as a number), so you do not need to set it to 0.
awk '{sum += $10} END {print sum}' access.log41298If you print the sum in the action that runs on each line instead of in END, each line shows the running total so far. Printing the line number and t each time the quantity times the unit price is added gives seven lines without the header, and the 886.3 on the last line is the grand total.
awk -F, 'NR > 1 {t += $3 * $4; print NR, t}' sales.csv2 90
3 189.5
4 378.5
5 423.5
6 468.5
7 508.3
8 886.3For an average, divide the sum by the number of lines in END. The five scores added up to 343, and the average was 68.6.
awk '{total += $3; n++} END {print total, n, total / n}' scores.txt343 5 68.6BEGIN is for printing a header line once above the results. The item total line appears only once above the items and amounts (quantity × unit price).
awk -F, 'BEGIN {print "item", "total"} NR > 1 {print $2, $3 * $4}' sales.csvitem total
keyboard 90
mouse 99.5
monitor 189
keyboard 45
cable 45
mouse 39.8
monitor 378Change the output separator with the OFS variable in BEGIN. Setting OFS to a comma puts a comma instead of a space where the commas in print are, so the account list comes out in CSV form.
awk 'BEGIN {FS = ":"; OFS = ","} {print $1, $3, $7}' users.txtroot,0,/bin/bash
bin,1,/sbin/nologin
daemon,2,/sbin/nologin
nobody,65534,/sbin/nologin
user,1000,/bin/bash
deploy,1001,/bin/bash
backup,1002,/sbin/nologinTo line up columns, use printf. %-10s is a left-aligned 10-character string, %3d is a 3-character integer, and %8.2f is a number fitted to 8 characters with two decimal places.
awk -F, 'NR > 1 {printf "%-10s %8.2f\n", $2, $3, $3 * $4}' sales.csvkeyboard 2 90.00
mouse 5 99.50
monitor 1 189.00
keyboard 1 45.00
cable 10 45.00
mouse 2 39.80
monitor 2 378.00Unlike print, printf does not end the line, so put [\n] at the end of the format. Without [\n], the item names run together on one line.
awk -F, 'NR > 1 {printf "%s ", $2}' sales.csvkeyboard mouse monitor keyboard cable mouse monitor5️⃣ Log analysis and awk command options
🔲 Log analysis
Adding to one variable gives only one total, so to count by client IP or by status code, collect the values in an array. n[$1]++ adds 1 to the array slot keyed by the IP, and for (ip in n) in END takes the collected slots out one by one.
awk '{n[$1]++} END {for (ip in n) print n[ip], ip}' access.log | sort -rn4 192.0.2.10
3 203.0.113.25
3 198.51.100.7
2 192.0.2.44The order in which for (ip in n) takes them out is not defined (per the GNU Awk manual), so we sorted them by request count with sort -rn. 192.0.2.10 had the most, with 4 requests.
Status codes are counted the same way. There were three 404 responses and one 500 response.
awk '{n[$9]++} END {for (s in n) print s, n[s]}' access.log | sort200 6
301 1
304 1
404 3
500 1Extracting only the status codes and passing them to sort and uniq -c gives the same counts.
awk '{print $9}' access.log | sort | uniq -c 6 200
1 301
1 304
3 404
1 500To see only error responses, add a condition on the status code. The lines of 400 and above were three requests looking for wp-login.php and .env, and one 500 error.
awk '$9 >= 400 {print $9, $1, $7}' access.log404 203.0.113.25 /wp-login.php
404 203.0.113.25 /wp-login.php
404 203.0.113.25 /.env
500 198.51.100.7 /api/search?q=awkBytes sent per IP are also totaled in an array. 192.0.2.10 received the most, at 31340 bytes.
awk '{b[$1] += $10} END {for (ip in b) print b[ip], ip}' access.log | sort -rn31340 192.0.2.10
5120 192.0.2.44
4250 198.51.100.7
588 203.0.113.25The time field ($4) can be compared as a string. Among lines with the same date and format, string order matches time order, so we selected only the four requests from 00:05 to just before 00:10.
awk '$4 >= "[29/Oct/2026:00:05" && $4 < "[29/Oct/2026:00:10" {print $4, $7}' access.log[29/Oct/2026:00:05:30 /contact.html
[29/Oct/2026:00:07:02 /old-page.html
[29/Oct/2026:00:07:15 /.env
[29/Oct/2026:00:09:48 /index.html🔲 awk command options
📌 Main options: results from this article
| Option | Task | Result in this article |
|---|---|---|
| -F fs | Sets the input field separator | -F: splits the account name ($1) and login shell ($7) in users.txt; -F, prints the item and quantity from sales.csv along with the header (item qty) |
| -v var=val | Assigns a value to a variable before the program runs | Passing the shell variable min as -v min=$min prints six lines with 1000 or more bytes sent; with -v FPAT=…, a quoted column in quoted.csv comes out as one field |
| -i inplace | Saves the output to the original file instead of the screen | After running gawk -i inplace, nothing appears on screen and the copy of scores.txt changes to two columns, name and score; adding -v inplace::suffix=.bak keeps the previous content in a .bak file |
| --version | Prints version information | The first line of [awk --version] shows GNU Awk 5 |
📎 Other options: per the official manual
| Option | Task |
|---|---|
| -f program-file | Reads the AWK program from a file instead of the first command-line argument; -f can be given several times |
| -e program-text | Uses the text on the command line as AWK program source; can be used with library functions loaded by -f or --include |
| -E file | Similar to -f, but processed last and blocks command-line variable assignments; for #! scripts |
| --traditional | Runs in compatibility mode, without recognizing GNU extensions |
| --posix | Turns on compatibility mode and adds more restrictions, for example not recognizing func, the synonym for function |
| --lint | Warns about constructs that are dubious or hard to port to other AWK implementations; with --lint=fatal, warnings become fatal errors |
| --dump-variables | Writes a sorted list of global variables with their types and final values to a file; awkvars.out if no file is given |
🔲 awk command simulator
In the simulator below, you can type awk commands yourself on the same test files.
🔢 FAQ & Recommended Content
Sorting
Linux sort command: sorting files
The sort command sorts file contents alphabetically or by number. Use it next to view awk totals from highest to lowest.
Removing duplicates
Linux uniq command
The uniq command reduces repeated adjacent lines to one and, with -c, counts how many times each repeated.
Viewing files
Linux head command & tail command
The head and tail commands show only the first or last few lines of a file. They are often used to check a server's access log.
ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.