opscape

Text processing

expr

Evaluate an integer, comparison or string expression given as separate arguments and print the result.

expr EXPRESSION

10 of 10 examples run in Debian 13.7 on 2026-10-31 · coreutils 9.7-3 · manual: expr(1)

expr is how Bourne shell scripts did arithmetic and string matching before shells had it built in. Each operand and operator must be a separate argument, so spaces around operators are mandatory, and operators the shell treats specially (*, <, >, |, &, (, )) must be escaped or quoted.

In new scripts, prefer the shell's own tools: $(( )) for arithmetic, ${#var} for length, [[ $s =~ re ]] or case for matching. They are faster (no new process) and need no escaping, and $(( )) is POSIX, so it works in sh too. You still meet expr in old scripts and installers, and its : match operator remains a compact way to extract part of a string in plain sh.

The exit status is the trap: expr exits with 1 when the result is 0 or empty, even though nothing went wrong, and 2 for a real error. Under set -e, i=$(expr $i - 1) reaching zero ends the script.

Examples

Do integer arithmetic

expr 7 + 5
expr 7 / 2
expr 7 % 2
output
12
3
1

Integer math only: 7 / 2 truncates to 3 and % gives the remainder.

Spaces between arguments are required

expr 7+5
explain
output
7+5

Without spaces, 7+5 is one argument, a string, and expr just prints it back.

Escape the multiplication sign

expr 6 \* 7
expr 6 '*' 7
output
42
42

An unescaped * would be expanded by the shell into the file names in the current directory. A backslash or quotes pass it through to expr.

A result of zero sets exit status 1

expr 5 - 5; echo "exit status: $?"
expr 5 - 4; echo "exit status: $?"
output
0
exit status: 1
1
exit status: 0

expr prints 0 and returns 1, which a script with set -e treats as failure. A non-zero, non-empty result returns 0.

Compare numbers and strings

expr 10 \> 9
expr 10 \> 9a
expr abc \< abd
output
1
0
1

Comparisons print 1 for true and 0 for false. When both sides are integers they compare numerically; otherwise as strings, so 10 is not greater than 9a (1 sorts before 9).

Match the start of a string with the colon operator

expr 'backup-20260314.tar.gz' : 'backup-'
expr 'backup-20260314.tar.gz' : '[a-z]*'
output
7
6

STRING : REGEX anchors the basic regex at the start of the string and prints how many characters matched: 7 for backup-, 6 for the run of letters.

Extract part of a string with a group

file='backup-20260314.tar.gz'
expr "$file" : 'backup-\([0-9]*\)'
expr "$file" : '.*\.\(.*\)'
output
20260314
gz

With a \( \) group, expr prints the captured text instead of a count: the date, then the last extension. .*\. is greedy, so it runs to the last dot. In bash, ${file##*.} does the second one without a process.

String length, substring and index

expr length 'hello world'
expr substr 'hello world' 7 5
expr index 'hello world' 'ow'
output
11
world
5

These keywords are GNU extensions. substr counts from 1, and index returns the position of the first character found from the set ow, which is the o at position 5.

Old-style loop counter

i=0
while [ "$i" -lt 3 ]; do i=$(expr "$i" + 1); echo "pass $i"; done
j=0; while [ "$j" -lt 3 ]; do j=$((j + 1)); echo "modern $j"; done
output
pass 1
pass 2
pass 3
modern 1
modern 2
modern 3

Both loops count to 3 under sh. The first starts a new expr process per iteration; the second uses POSIX shell arithmetic, which is built in and the one to write today.

Non-numeric arguments are an error

expr 5 + five
explain
outputexit 2
expr: non-integer argument

An operand that is not an integer in an arithmetic expression gives an error message and exit status 2, which is how you tell a real error from a zero result.

Common pitfalls

  • Exit status 1 for a result of 0 or an empty string breaks set -e scripts and if expr ... tests. Check the output, not the status, or use $(( )).

  • Unescaped *, >, <, ( and | are interpreted by the shell first: expr 3 > 2 creates a file named 2.

  • If a string operand happens to look like an operator or keyword (length, +, match), expr misparses the expression. GNU expr accepts + STRING to force it to be read as a string.

  • expr runs a separate process for every call. In loops, shell arithmetic is orders of magnitude faster.

GNU vs BSD and macOS

The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).

  • length, substr, index, match and the + quoting token are GNU extensions; BSD/macOS expr supports only the POSIX operators. Use expr "$s" : '.*' for a portable length.

Options worth knowing

A selection, described in our own words. For the full list, see expr(1) or expr --help.

OptionWhat it does
--helpShow usage and exit.
--versionShow version information and exit.

Related