Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Bash & Python Automation By James Joyner IV · · 8 min read Last reviewed Jul 2026

Bash Error Guide: 'integer expression expected' — Fix Numeric Test Comparisons

Quick answer

Fix 'integer expression expected' in Bash: handle empty or non-numeric variables in -eq/-lt tests, quote and default values, and use arithmetic tests correctly.

  • #bash
  • #automation
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Bash & Python Automation error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

Bash raises this whenever a numeric test comparison operator (-eq, -ne, -lt, -le, -gt, -ge) is handed something that is not an integer. The classic trigger is an empty or non-numeric variable inside [ ... ]:

./check.sh: line 12: [: foo: integer expression expected

The empty-variable variant is even more common and confusing, because the offending value is invisible:

./check.sh: line 12: [: : integer expression expected

That empty space after the colon is the value — a variable that expanded to nothing. The word before “integer expression expected” is exactly what Bash tried and failed to read as a number.

Symptoms

  • A script that works with normal input fails when a variable is empty, unset, or contains text.
  • The error names a value that clearly isn’t a number (foo, 12.5, 10G, 3).
  • The error shows an empty value ([: : integer expression expected) — a variable expanded to nothing.
  • Comparing counts, exit codes, sizes, or parsed command output intermittently breaks depending on the data.
  • Floating-point or human-readable numbers (3.14, 2K, 1,000) always fail because test only understands base-10 integers.

Common Root Causes

  • Empty or unset variable — the variable was never set, a command produced no output, or a typo left it undefined; with [ it expands to nothing and fails the numeric test.
  • Non-numeric content — the variable holds text, a float, a unit suffix (10G), a comma-grouped number, or has leading/trailing whitespace or newlines.
  • Unquoted command substitution with extra whitespace$(wc -l < file) on some systems yields leading spaces or a trailing newline that breaks the comparison.
  • Comparing strings with numeric operators — using -eq to compare strings (should be =), or =/< to compare numbers (should be -eq/-lt).
  • Locale or formatting — numbers formatted with thousands separators or a decimal comma.
  • Word-splitting surprises — an unquoted variable containing multiple words turns [ $x -gt 5 ] into a malformed test with too many arguments or the wrong token in the numeric slot.

Diagnostic Workflow

Reproduce and inspect the exact value, making whitespace visible:

printf 'value=[%s]\n' "$count"     # brackets reveal empty strings and stray spaces
printf 'bytes=[%q]\n' "$count"     # %q shows tabs/newlines/quoting explicitly

Trace which line and which value the shell chokes on:

bash -x ./check.sh 2>&1 | grep -n 'integer expression'   # -x prints each test with expanded values

Test whether the value is actually a clean integer:

[[ "$count" =~ ^-?[0-9]+$ ]] && echo "integer" || echo "NOT an integer: [$count]"

Check for the common command-substitution whitespace trap:

lines=$(wc -l < /etc/hosts)
printf 'lines=[%s]\n' "$lines"     # some wc builds pad with leading spaces

If the source is a float or has a suffix, confirm what produced it:

echo "$count" | od -c | head       # reveals hidden characters, newlines, and CR (\r) from Windows files

Example Root Cause Analysis

A deploy gate counted ready pods and compared against a threshold:

ready=$(kubectl get pods -l app=web --field-selector=status.phase=Running --no-headers | wc -l)
if [ "$ready" -ge 3 ]; then
  echo "enough replicas"
fi

Intermittently it failed with:

deploy-gate.sh: line 3: [: : integer expression expected

The clue was the empty value. When the label selector matched no pods, kubectl printed nothing to stdout but also emitted No resources found to stderr — so wc -l counted zero lines and ready became 0… except on the runs that failed, an authentication hiccup made kubectl exit non-zero and print nothing at all, leaving ready empty. [ "" -ge 3 ] is exactly the empty-variable case.

The fix defends the comparison against non-integer input rather than trusting upstream output:

ready=$(kubectl get pods -l app=web --field-selector=status.phase=Running --no-headers 2>/dev/null | wc -l)
ready=${ready//[^0-9]/}        # strip any non-digits (whitespace, stray chars)
ready=${ready:-0}              # default to 0 if empty

if [[ "$ready" =~ ^[0-9]+$ ]] && [ "$ready" -ge 3 ]; then
  echo "enough replicas: $ready"
else
  echo "replica check failed or too few ready: [$ready]" >&2
  exit 1
fi

Now an empty or garbage value is normalized to 0 and validated before the numeric comparison ever runs, so the gate fails clean instead of erroring.

Prevention Best Practices

  • Default every numeric variable — use ${var:-0} so an empty value becomes a valid integer instead of blowing up the test.
  • Validate before comparing — guard numeric tests with [[ "$var" =~ ^-?[0-9]+$ ]] so non-numeric input is rejected with a clear message.
  • Always quote[ "$x" -gt 5 ], never [ $x -gt 5 ]; quoting prevents word-splitting and turns an empty value into a single empty argument rather than a syntax error.
  • Use [[ ]] and (( )) — Bash’s [[ ]] is more forgiving, and arithmetic context (( x >= 3 )) treats an empty/undefined value as 0 (with set -u off) and reads numbers naturally.
  • Strip whitespace from command outputvar=${var//[^0-9]/} or read-trim before comparing counts from wc, grep -c, etc.
  • Handle floats separatelytest is integer-only; use awk 'BEGIN{exit !('"$a"' > '"$b"')}' or bc -l for floating-point comparisons.
  • Watch for CRLF — files edited on Windows add \r; run dos2unix or strip \r so 3\r isn’t treated as non-numeric.

Quick Command Reference

printf '[%s]\n' "$var"                 # reveal empty strings / stray whitespace
printf '[%q]\n' "$var"                 # reveal tabs, newlines, CR
[[ "$var" =~ ^-?[0-9]+$ ]]             # is it a clean integer?
var=${var:-0}                          # default empty/unset to 0
var=${var//[^0-9]/}                    # strip non-digit characters
(( var >= 3 )) && echo ok              # arithmetic comparison
bash -x ./script.sh                    # trace expanded test values
echo "$var" | od -c                    # find hidden characters / CR

Conclusion

integer expression expected means a numeric test operator received something that isn’t a base-10 integer — most often an empty variable, stray whitespace, a \r from a Windows file, or a value that was always text or a float. The fix is defensive input handling: quote the variable, default it with ${var:-0}, strip non-digits, and validate with a regex before comparing. Reach for (( )) arithmetic context for numbers and [[ ]] string tests for text, and reserve -eq/-lt for values you have already proven are integers.

Free download · 368-page PDF

Fixed it? Get 500 Bash & Python Automation & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.