gsub() is AWK's find-and-replace. It replaces every match of a pattern in a line or in a single column, and it tells you how many replacements it made. It looks like sed 's/old/new/g', with one big advantage: it can work on one column and leave the rest alone. This guide covers the syntax, the special characters that trip people up, and how to use it with columns and shell variables, with the exact output of each example.
The quick answer
awk '{gsub(/foo/, "X"); print}' file.txt # replace every foo with X on every line
awk '{sub(/foo/, "X"); print}' file.txt # replace only the first foo on each line
awk '{gsub(/foo/, "X", $2); print}' file.txt # replace only inside column 2
If AWK is new to you, read the beginner's guide first.
Sample file
printf 'foo bar foo\nbaz foo\nno match\n' > g.txt
Syntax
gsub(regex, replacement) # works on the whole line, $0
gsub(regex, replacement, target) # works on $2, a variable, or any other target
sub(regex, replacement, target) # same, but replaces only the first match
Both functions change the target in place and return the number of replacements (sub returns 0 or 1). The g in gsub stands for "global", meaning all matches.
sub() versus gsub()
awk '{sub(/foo/, "X"); print}' g.txt
# X bar foo
# baz X
# no match
awk '{gsub(/foo/, "X"); print}' g.txt
# X bar X
# baz X
# no match
The first line has two foo. sub() changed one, gsub() changed both.
Count the replacements
awk '{n = gsub(/foo/, "X"); print n}' g.txt
# 2
# 1
# 0
awk '{n += gsub(/foo/, "X")} END {print n}' g.txt
# 3
Because gsub() returns the count, it doubles as a way to count occurrences. There is more on that in the counting occurrences guide.
Change only some lines
Put a pattern before the braces, or use the return value as the condition. A rule with no braces prints the line when the condition is true, so this prints only the lines that were changed:
awk '/foo/ {gsub(/foo/, "X"); print}' g.txt # change and print only matching lines
awk 'gsub(/foo/, "X")' g.txt # same result, shorter
# X bar X
# baz X
To change matching lines but still print every line, put a lone 1 after the replacement, which is an always-true condition that prints the line:
awk '{gsub(/foo/, "X")} 1' g.txt
Replace inside one column
Pass the field as the third argument:
echo 'a,foo,c' | awk -F, '{gsub(/foo/, "X", $2); print}'
# a X c
Look at the output. The commas turned into spaces. When you change a field, AWK rebuilds the whole line from its fields and joins them with the output field separator, OFS, which is a space by default. Set OFS to match the input:
echo 'a,foo,c' | awk -F, -v OFS=, '{gsub(/foo/, "X", $2); print}'
# a,X,c
The same applies to space-separated input with uneven spacing. a foo c comes out as a X c, with single spaces, after a field is modified. This is the most common surprise with gsub() on columns. For more on separators, see AWK field separators.
A practical example, converting the dashes in the first column of a CSV line to slashes:
echo '2026-10-30,x' | awk -F, -v OFS=, '{gsub(/-/, "/", $1); print}'
# 2026/10/30,x
Use the matched text with &
In the replacement, & stands for whatever the regex matched:
echo 'cat dog' | awk '{gsub(/[a-z]+/, "<&>"); print}'
# <cat> <dog>
To get a literal ampersand, escape it with two backslashes inside the AWK string:
echo 'cat' | awk '{gsub(/cat/, "\\&"); print}'
# &
A replacement of a&b on the text cat gives acatb, not a&b. This matters when the replacement comes from user data, such as a URL with & in it.
Special characters: the dot, slashes and plus signs
The pattern is a regular expression, so some characters mean something other than themselves.
echo 'a.b.c' | awk '{gsub(".", "-"); print}'
# -----
echo 'a.b.c' | awk '{gsub(/\./, "-"); print}'
# a-b-c
echo 'a.b.c' | awk '{gsub("\\.", "-"); print}'
# a-b-c
An unescaped . matches any character, so the first command replaced all five characters. Escape it with a backslash in a regex literal (/\./) or with two backslashes in a string ("\\."). A character class also works: [.].
A slash in a regex literal must be escaped, or you can use a string instead:
echo '/usr/local/bin' | awk '{gsub(/\//, "_"); print}' # _usr_local_bin
echo '/usr/local/bin' | awk '{gsub("/", "_"); print}' # _usr_local_bin
Other characters that need escaping or brackets to be treated literally include + * ? ( ) [ ] ^ $ | and the backslash itself.
Common recipes
# delete characters
echo 'a1b22c333' | awk '{gsub(/[0-9]/, ""); print}' # abc
# trim spaces from both ends
echo ' hi there ' | awk '{gsub(/^ +| +$/, ""); print "[" $0 "]"}' # [hi there]
# squeeze repeated spaces into one
echo 'a b c' | awk '{gsub(/ +/, " "); print}' # a b c
# tabs to commas
printf 'a\tb\tc\n' | awk '{gsub(/\t/, ","); print}' # a,b,c
# replace every run of digits
echo 'order 123 and 45' | awk '{gsub(/[0-9]+/, "#"); print}' # order # and #
# strip punctuation
echo 'Hi, there! 99' | awk '{gsub(/[[:punct:]]/, ""); print}' # Hi there 99
# remove Windows line endings
printf 'ab\r\n' | awk '{sub(/\r$/, ""); print}' # ab
Anchors work as in other regex tools. On foo foo, sub(/^foo/, "X") gives X foo and sub(/foo$/, "X") gives foo X.
Use shell variables with gsub
Pass the value with -v and use it as the replacement or as the pattern:
awk -v r=NEW '{gsub(/foo/, r); print}' g.txt
# NEW bar NEW
# baz NEW
# no match
awk -v p=foo '{gsub(p, "X"); print}' g.txt
# X bar X
# baz X
# no match
Three things to watch:
-
A pattern in a variable is still a regex. With
-v p='a+b', the texta+bwas not replaced, because the pattern means "one or more a, then b". With-v p='a[+]b'it was replaced. Escape special characters in the variable. -
An
&in the variable is special again. With-v r='A&B',foobecameAfooB. To get a literal&, pass-v r='A\\&B'; the result wasA&B. -
-vprocesses backslash escapes.-v r='a\tb'puts a real tab in the variable.
Case-insensitive replacement
AWK regexes are case sensitive. The portable approach is to work on a lowercased copy, which changes the case of the whole line:
echo 'Foo FOO foo' | awk '{t = tolower($0); n = gsub(/foo/, "X", t); print n, t}'
# 3 X X X
This counts all three variants. If you need to keep the rest of the line's original case, use gawk and its IGNORECASE variable. mawk silently ignores that variable, so the output would be wrong without any error.
Capture groups: gensub() in gawk only
sub() and gsub() cannot reuse parts of the match, as \1 does in sed. gawk adds gensub() for that:
echo 'john smith' | gawk '{print gensub(/(\w+) (\w+)/, "\\2, \\1", "g")}'
# smith, john
On mawk, the same function fails with function gensub never defined. For portable code, split the line into fields instead:
echo 'john smith' | awk '{print $2 ", " $1}'
# smith, john
To pull a piece out of a line rather than replace it, match() with substr() works everywhere:
echo 'id=42;x' | awk '{if (match($0, /[0-9]+/)) print substr($0, RSTART, RLENGTH)}'
# 42
Change the file itself
gsub() only changes the text AWK holds in memory. The file on disk stays the same until you save the output. Write to a new file and move it into place:
awk '{gsub(/foo/, "X")} 1' g.txt > g.tmp && mv g.tmp g.txt
On gawk you can edit in place. I ran this on gawk 5.2.1:
gawk -i inplace '{gsub(/foo/, "X")} 1' g.txt
Never redirect to the file you are reading (awk ... g.txt > g.txt), because the shell empties it first.
gsub or sed?
For a plain global replacement on whole lines, sed 's/foo/X/g' file is shorter and gives the same result. Choose AWK when you need to change only one column, replace only on lines that meet a condition on another column, count replacements, or combine the replacement with other calculations. Capture groups are easier in sed (\1), or in gawk with gensub().
Common problems
-
Every character was replaced. You used
"."or/./. Escape the dot. -
Commas or spacing changed after editing a column. Set
OFSto the same value as-F. -
The file did not change.
gsub()only affects the output. Save it with a new file or-i inplaceon gawk. -
Nothing matched with a variable pattern. The variable holds a regex. Escape
+ ? . ( ) [ ] ^ $ |or use a bracket class. -
Unexpected
&in the output. It is replaced by the matched text. Escape it as\\&. -
Whole line changed after modifying
$0. If you change$0withgsub(), AWK splits it into fields again. Aftergsub(/ /, ",")ona b c,NFwas 1 and$1wasa,b,c.
Frequently asked questions
What is the difference between sub and gsub in AWK?
sub() replaces the first match in the target. gsub() replaces every match.
How do I replace text in one column only?
Give the column as the third argument: gsub(/old/, "new", $3). Set OFS to your delimiter so the line is rebuilt correctly.
How do I use a variable in gsub?
Pass it with -v, for example awk -v r=NEW '{gsub(/foo/, r)} 1' file. The pattern and the replacement can both be variables.
Does AWK gsub support regex groups like \1?
No. Standard AWK has no backreferences in the replacement. gawk provides gensub(), which does, but it is not available in mawk.
How do I replace a literal dot or slash?
Escape it: /\./ and /\// in a regex literal, or "\\." in a string. A bracket such as [.] also works.
Next in this series
- AWK Command in Linux: A Beginner's Guide with Examples
- How to Print a Column with AWK
- AWK Remove Duplicates: Delete Duplicate Lines, Columns and CSV Rows
- AWK Cheat Sheet
Originally published on DEV Talk.
Top comments (0)