When working with Bash loops, you sometimes encounter an item that you don't want to process. You don't necessarily want to stop the loop—you simply want to skip the rest of the current iteration and move on.
That's what the Bash continue statement is for.
Unlike break, which exits the loop completely, continue skips the remaining commands in the current iteration and starts the next one.
A simple way to remember it:
continue → skip this iteration
break → leave the loop
Basic Syntax
The simplest form is:
continue
Bash also supports:
continue n
where n specifies which enclosing loop should receive the continuation. This is mainly useful with nested loops and we'll look at it later.
A Simple Example
Let's start with a small loop:
for i in {1..5}
do
if [ "$i" -eq 3 ]; then
continue
fi
echo "Processing $i"
done
The output is:
Processing 1
Processing 2
Processing 4
Processing 5
When i becomes 3, Bash executes:
continue
Everything below it in that iteration is skipped, including the echo command. Bash then starts the next iteration with i=4.
Notice that the loop itself is still running.
Printing Only Even Numbers
The original example is a good way to demonstrate exactly what continue does.
Suppose we want to loop through the numbers from 1 to 10, but print only the even numbers:
#!/bin/bash
for i in {1..10}
do
mod=$(($i % 2))
if [ "$mod" -ne 0 ]; then
continue
fi
echo "i=$i"
done
The result is:
i=2
i=4
i=6
i=8
i=10
The % operator returns the remainder of a division.
For example:
1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
4 % 2 = 0
Therefore, an odd number produces a non-zero remainder.
When that happens:
if [ "$mod" -ne 0 ]; then
continue
fi
skips the rest of the current iteration.
For an even number, the condition is false, so execution reaches:
echo "i=$i"
This gives us the desired output.
A more Bash-oriented version
Since Bash has arithmetic evaluation, the same example can be written more compactly:
#!/bin/bash
for i in {1..10}
do
if (( i % 2 != 0 )); then
continue
fi
echo "i=$i"
done
The purpose here isn't to find the shortest possible solution. The continue is deliberately visible because it demonstrates the control flow.
Filtering Items with continue
A very common pattern is to use continue as an early filter:
for item in ...
do
if item_should_be_skipped; then
continue
fi
process "$item"
done
This is particularly useful when a loop has several reasons why an item shouldn't be processed.
For example, suppose we want to process only regular files:
for file in *
do
if [ ! -f "$file" ]; then
continue
fi
echo "Processing file: $file"
done
Directories and other filesystem entries are ignored, while regular files reach the processing code.
This can be easier to read than putting the entire body inside an if block.
Skipping Empty Lines
continue is also useful when processing text.
For example:
while IFS= read -r line
do
if [ -z "$line" ]; then
continue
fi
echo "Processing: $line"
done < input.txt
Empty lines are simply ignored.
The same idea can be used to ignore comments:
while IFS= read -r line
do
if [ -z "$line" ]; then
continue
fi
if [[ "$line" == \#* ]]; then
continue
fi
echo "Configuration: $line"
done < config.txt
Only lines that pass both checks reach the processing code.
Filtering Files
Another common example is processing only files with a particular extension:
for file in *
do
if [[ "$file" != *.log ]]; then
continue
fi
echo "Processing log: $file"
done
If the directory contains:
app.log
database.log
image.png
notes.txt
server.log
only the .log files are processed.
This pattern is particularly convenient when a loop performs several validation checks before doing its real work.
continue in a while Loop
continue works with while loops as well:
counter=0
while [ "$counter" -lt 10 ]
do
counter=$((counter + 1))
if [ "$counter" -eq 5 ]; then
continue
fi
echo "counter=$counter"
done
Output:
counter=1
counter=2
counter=3
counter=4
counter=6
counter=7
counter=8
counter=9
counter=10
There is one important detail to watch for with while loops: make sure the code that advances the loop condition isn't accidentally skipped.
For example, this is dangerous:
counter=0
while [ "$counter" -lt 10 ]
do
if [ "$counter" -eq 5 ]; then
continue
fi
counter=$((counter + 1))
done
Once counter reaches 5, the continue runs before the counter is incremented. The loop then starts another iteration with the same value, and the condition remains true forever.
The safe version performs the update first:
counter=0
while [ "$counter" -lt 10 ]
do
counter=$((counter + 1))
if [ "$counter" -eq 5 ]; then
continue
fi
echo "counter=$counter"
done
This is an important practical rule: a continue must not accidentally skip whatever makes a while loop progress toward termination.
continue in an until Loop
The same mechanism works with until:
counter=0
until [ "$counter" -ge 5 ]
do
counter=$((counter + 1))
if [ "$counter" -eq 3 ]; then
continue
fi
echo "counter=$counter"
done
Output:
counter=1
counter=2
counter=4
counter=5
Only the iteration where counter is 3 is skipped.
continue in a select Loop
Bash's select construct also supports continue.
For example:
select choice in start stop restart quit
do
if [ "$choice" = "quit" ]; then
break
fi
if [ -z "$choice" ]; then
continue
fi
echo "You selected: $choice"
done
Here, an invalid or empty selection causes the menu to be displayed again, while selecting quit leaves the loop.
This is a useful illustration of the different roles of continue and break without needing another long explanation of either command.
Nested Loops
With nested loops, an ordinary continue affects the innermost loop.
For example:
for outer in {1..3}
do
for inner in {1..3}
do
if [ "$inner" -eq 2 ]; then
continue
fi
echo "outer=$outer inner=$inner"
done
done
Output:
outer=1 inner=1
outer=1 inner=3
outer=2 inner=1
outer=2 inner=3
outer=3 inner=1
outer=3 inner=3
When inner is 2, only that iteration of the inner loop is skipped. The outer loop continues normally.
continue n
Bash provides an optional numeric argument when nested loops need different behavior:
continue n
For example:
for outer in {1..3}
do
for inner in {1..3}
do
if [ "$inner" -eq 2 ]; then
continue 2
fi
echo "outer=$outer inner=$inner"
done
echo "Finished outer=$outer"
done
Here:
continue 2
continues the second enclosing loop instead of merely continuing the innermost one.
The result is that when inner reaches 2, the remainder of the current outer-loop iteration is skipped as well.
Although this can be useful, continue 2 and larger values should be used sparingly. If a reader has to count several nested loops to understand where execution goes, restructuring the code may make the logic clearer.
A Practical Example
Let's put the idea to work in a more realistic script.
Suppose we receive a list of files and want to process only files that:
- exist,
- are readable, and
- have a
.logextension.
#!/bin/bash
for file in "$@"
do
if [ ! -e "$file" ]; then
echo "Skipping missing file: $file"
continue
fi
if [ ! -r "$file" ]; then
echo "Skipping unreadable file: $file"
continue
fi
if [[ "$file" != *.log ]]; then
echo "Skipping non-log file: $file"
continue
fi
echo "Processing log: $file"
# Actual log processing goes here
done
Each continue removes an unsuitable item from consideration.
Once a file passes all three checks, the script reaches the actual processing code.
This structure is often cleaner than nesting the processing code inside three levels of if statements.
continue vs break
The difference is simple enough that we don't need a complicated example.
continue
means:
Skip the rest of this iteration.
While:
break
means:
Leave the loop completely.
For example, when searching for something:
for file in *
do
if [ "$file" = "important.txt" ]; then
echo "Found it!"
break
fi
done
Once the desired file is found, there is no reason to continue searching.
On the other hand, if a particular file should simply be ignored, continue is appropriate:
for file in *
do
if [ "$file" = "temporary.txt" ]; then
continue
fi
process "$file"
done
The decision is straightforward:
Skip this item? → continue
Finished here? → break
continue, return, and exit
These commands operate at different levels of control flow:
| Command | Effect |
|---|---|
continue | Skip the current loop iteration |
break | Exit the current loop |
return | Leave a function |
exit | Exit the script or shell |
For example:
process_file()
{
if [ ! -f "$1" ]; then
return 1
fi
echo "Processing $1"
}
Here return is appropriate because we're leaving a function, not skipping an iteration of a loop.
Keeping these four commands conceptually separate makes Bash control flow much easier to understand.
Common Mistake: Using continue Outside a Loop
continue requires a loop context.
This doesn't make sense:
#!/bin/bash
echo "Hello"
continue
Bash will report an error because there is no loop to continue.
Likewise, if your intention is to leave a function, use return; if you need to terminate the entire script, use exit.
A Useful Design Pattern
One of the nicest ways to use continue is to put validation at the beginning of a loop:
for item in "$@"
do
if ! valid_item "$item"; then
continue
fi
process_item "$item"
done
With several checks:
for item in "$@"
do
if [ -z "$item" ]; then
continue
fi
if ! valid_item "$item"; then
continue
fi
if should_ignore "$item"; then
continue
fi
process_item "$item"
done
The loop effectively becomes:
check → reject → check → reject → process
The normal processing path remains easy to find, while unwanted cases leave the loop iteration early.
This is one of the main reasons continue can improve readability rather than simply adding another control-flow statement to the script.
Getting Help
Since continue is part of Bash itself, its documentation is available directly from the shell:
help continue
You can also check its type:
type continue
Bash will identify it as a shell keyword.
Quick Reference
continue
Skip the remainder of the current iteration.
continue 2
Continue the second enclosing loop.
Typical usage:
for item in ...
do
if should_skip "$item"; then
continue
fi
process "$item"
done
And the important distinction:
continue → skip this iteration
break → exit the loop
Conclusion
continue gives Bash loops a clean way to reject individual items without stopping the entire loop.
It is especially useful when processing:
- files
- command-line arguments
- configuration files
- lines of text
- lists of values
- records that require validation
The basic pattern is simple:
if something_should_be_skipped; then
continue
fi
Everything below that point is reserved for items worth processing.
In other words, continue is Bash's polite way of saying:
“Not this one. Let's move on.”