Text processing
sed, awk, cut, sort, uniq, tr, xargs, column, paste, wc, tee.
sed
Substitute
First on each line:
sed 's/foo/bar/' file.txtAll on each line:
sed 's/foo/bar/g' file.txtCase-insensitive:
sed 's/foo/bar/gi' file.txtIn-place edit:
sed -i 's/foo/bar/g' file.txtIn-place with backup:
sed -i.bak 's/foo/bar/g' file.txt
Target lines
Single line:
sed '5s/foo/bar/' file.txtRange:
sed '10,20s/foo/bar/g' file.txtFrom pattern to EOF:
sed '/START/,$ s/foo/bar/g' file.txtBetween patterns:
sed '/BEGIN/,/END/ s/foo/bar/g' file.txt
Delete lines
One line:
sed '5d' file.txtRange:
sed '10,20d' file.txtComments:
sed '/^#/d' file.txtEmpty lines:
sed '/^$/d' file.txt
Insert / append / replace
Insert before line 3:
sed '3i\New line' file.txtAppend after line 3:
sed '3a\New line' file.txtBefore pattern:
sed '/pattern/i\Inserted' file.txtReplace matched line:
sed '/pattern/c\Replacement' file.txt
Print specific lines (-n + p)
Like grep:
sed -n '/pattern/p' file.txtLine 5:
sed -n '5p' file.txtRange:
sed -n '10,20p' file.txt
Multiple ops, alt delimiter
Multiple ops:
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txtAlt delimiter (paths):
sed 's|/usr/local|/opt|g' file.txtsed 's#http://#https://#g' file.txt
Capture groups
Basic regex (escape parens):
sed 's/\(.*\)=\(.*\)/\2=\1/' file.txtExtended (
-E):sed -E 's/(.*)=(.*)/\2=\1/' file.txt
Line trim / prefix / suffix
Prefix:
sed 's/^/PREFIX: /' file.txtSuffix:
sed 's/$/ # comment/' file.txtTrim leading WS:
sed 's/^[[:space:]]*//' file.txtTrim trailing WS:
sed 's/[[:space:]]*$//' file.txtBoth:
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' file.txt
Transliteration (like tr)
sed 'y/abc/ABC/' file.txt
awk
Print fields
$1, $2, … $0 is the line. $NF is last, $(NF-1) second-to-last.
One col:
awk '{print $1}' file.txtTwo cols:
awk '{print $1, $3}' file.txtLast col:
awk '{print $NF}' file.txt
Field separators
Colon input:
awk -F: '{print $1, $3}' /etc/passwdCSV:
awk -F',' '{print $2}' data.csvCustom output separator:
awk -F: -v OFS='\t' '{print $1, $3, $7}' /etc/passwd
Filter with patterns
Contains “error”:
awk '/error/' file.txtNot starting with #:
awk '!/^#/' file.txtNumeric compare:
awk '$3 > 100' file.txtString compare:
awk '$1 == "root"' /etc/passwdUID ≥ 1000:
awk -F: '$3 >= 1000' /etc/passwd
Built-in vars
| Var | Meaning |
|---|---|
NR | line number (across all files) |
FNR | line number (current file) |
NF | fields on current line |
FS/OFS | input/output field separator |
RS/ORS | input/output record separator |
Number lines:
awk '{print NR, $0}' file.txtCount lines:
awk 'END {print NR}' file.txtLong lines:
awk 'length > 80' file.txtBetween patterns (inclusive):
awk '/START/,/END/' file.txtSkip header:
awk 'NR > 1 {print $2}' data.tsv
Aggregation
Sum col:
awk '{sum += $3} END {print sum}' data.txtAverage:
awk '{sum += $1; n++} END {print sum/n}' nums.txtMax of col 3:
awk 'NR==1 || $3 > max {max=$3} END {print max}' data.txtFrequency count (e.g. requests per IP):
awk '{count[$1]++} END {for (k in count) print count[k], k}' access.log | sort -rn
Printf formatting
Aligned columns:
awk -F: '{printf "%-20s %s\n", $1, $7}' /etc/passwdReplace fields:
awk -F: -v OFS=':' '$3 == 0 {$1 = "SUPERUSER"} {print}' /etc/passwdMultiple rules:
awk '/error/ {errors++} /warning/ {warnings++} END {print errors, warnings}' log.txt
cut
By char range:
cut -c1-10 file.txtFrom char 5:
cut -c5- file.txtFirst 20 chars:
cut -c-20 file.txtOne delimited field:
cut -d: -f1 /etc/passwdMultiple fields:
cut -d: -f1,3,7 /etc/passwdField range:
cut -d',' -f2-4 data.csvComplement (everything except):
cut -d: --complement -f2 /etc/passwdChange output delimiter:
cut -d: -f1,7 --output-delimiter=$'\t' /etc/passwd
sort
Alphabetic:
sort file.txtReverse:
sort -r file.txtNumeric:
sort -n numbers.txtHuman (K/M/G):
sort -h sizes.txtVersion (1.10 > 1.2):
sort -V versions.txtMonth names:
sort -M dates.txtRandom shuffle:
sort -R file.txtUnique:
sort -u file.txtCase-insensitive:
sort -f file.txtAlready sorted? (exit code):
sort -c file.txtStable sort:
sort -s -k2,2 file.txt
By column (-k)
2nd col:
sort -k2 file.txtNumeric on 2nd col only:
sort -k2,2n file.txtBy UID in passwd:
sort -t: -k3,3n /etc/passwdMulti-key:
sort -k1,1 -k2,2n file.txtIPs correctly:
sort -t. -k1,1n -k2,2n -k3,3n -k4,4n ips.txt
uniq
uniq compares adjacent lines — always sort first.
Dedupe:
sort file.txt | uniqCount occurrences:
sort file.txt | uniq -cOnly duplicates:
sort file.txt | uniq -dOnly unique (appear once):
sort file.txt | uniq -uCase-insensitive:
sort file.txt | uniq -iSkip first N fields/chars:
sort file.txt | uniq -f 1sort file.txt | uniq -s 5Top 10 most frequent:
sort file.txt | uniq -c | sort -rn | head -10
tr
Lower → upper:
echo "hello" | tr 'a-z' 'A-Z'Spaces → underscores:
echo "hello world" | tr ' ' '_'Tabs → spaces:
cat file.txt | tr '\t' ' 'Squeeze repeats:
echo "aabbcc" | tr -s 'a-z'# abcSqueeze spaces:
echo "too many" | tr -s ' 'Delete chars:
echo "hello 123" | tr -d '0-9'Complement (invert set, keep only):
echo "hello 123" | tr -cd '0-9\n'Strip non-printable:
cat file.bin | tr -cd '[:print:]\n'DOS → Unix line endings:
tr -d '\r' < dosfile.txt > unixfile.txt
| Class | Matches |
|---|---|
[:alnum:] | letters + digits |
[:alpha:] | letters |
[:digit:] | digits |
[:lower:] | lowercase |
[:upper:] | uppercase |
[:space:] | whitespace |
[:print:] | printable |
[:punct:] | punctuation |
xargs
Pass stdin as args:
find . -name "*.txt" | xargs wc -lNull-separated (handles spaces):
find . -name "*.txt" -print0 | xargs -0 wc -lBatch size:
echo "1 2 3 4 5" | xargs -n 2 echoPlaceholder
{}:ls *.tar.gz | xargs -I {} tar xzf {} -C /tmp/extracted/Per-line:
cat urls.txt | xargs -I {} curl -O {}Parallel (4 procs):
find . -name "*.png" -print0 | xargs -0 -P 4 -n 1 optipngPrompt before each:
find . -name "*.bak" | xargs -p rmDry run (echo before exec):
find . -name "*.log" | xargs -t echo rmBulk find-and-replace:
grep -rl "TODO" src/ | xargs sed -i 's/TODO/DONE/g'
column
Auto-align:
mount | column -tTable with delimiter:
cat /etc/passwd | column -t -s ':'Vertical list → multi-col:
echo -e "one\ntwo\nthree\nfour\nfive\nsix" | columnCustom output separator:
column -t -s ',' -o ' | ' data.csvJSON (util-linux 2.37+):
echo '{"a":1,"b":2}' | column --json
paste
Side-by-side (tab):
paste file1.txt file2.txtCustom delimiter:
paste -d ',' file1.txt file2.txtAll lines into one:
paste -s file.txtAll lines with delimiter:
paste -sd ',' file.txtLines → N columns:
paste - - - < file.txt# 3 colscat file.txt | paste -d ',' - -# 2 cols, commaReorder columns (from same file):
paste <(cut -f3 data.tsv) <(cut -f1 data.tsv)
wc
Lines / words / chars:
wc file.txtLines:
wc -l file.txtWords:
wc -w file.txtChars / bytes:
wc -m file.txt/wc -c file.txtLongest line length:
wc -L file.txtCount files in dir:
ls | wc -l/fd -t f | wc -lTotal lines across files:
find . -name "*.py" -exec cat {} + | wc -l
tee
Write + pass through:
ls -la | tee listing.txtAppend:
echo "new entry" | tee -a log.txtMultiple files at once:
echo "hello" | tee file1.txt file2.txtInspect mid-pipeline:
cat data.txt | sort | tee sorted.txt | uniq -c | tee counts.txt | sort -rn | headWrite to sudo-owned file:
echo "new line" | sudo tee -a /etc/hosts > /dev/null
Common combinations
HTTP status code frequency:
awk '{print $9}' access.log | sort | uniq -c | sort -rnUnique IPs:
awk '{print $1}' access.log | sort -uMax in CSV col 3:
awk -F',' '{print $3}' data.csv | sort -n | tail -1Bulk find-and-replace:
grep -rl "oldtext" src/ | xargs sed -i 's/oldtext/newtext/g'Dedupe preserving order (no sort):
awk '!seen[$0]++' file.txtExtract
key=value:grep -oP '(?<=key=)\w+' config.txtNumbered list:
cat -n file.txtawk '{printf "%3d. %s\n", NR, $0}' file.txtJoin matching line with next:
sed '/pattern/{N;s/\n/ /}' file.txtEvery Nth line:
awk 'NR % 5 == 0' file.txtSorted diff:
diff <(sort file1.txt) <(sort file2.txt)