As an experienced Bash coder, I receive many questions on using goto statements in shell scripts. Though popular historically in early programming languages, goto has developed a stigma due to encouraging unstructured “spaghetti” code.
But when applied judiciously, goto retains situational usefulness even in modern Bash. This guide surveys common goto misuses in Bash, but also demonstrates patterns where goto shines to handle convoluted control flow concisely. Master BASHers equip many tools; understanding proper goto usage expands your scripting versatility.
Avoiding Common Goto Pitfalls
Unrestricted goto usage breeds chaotic and unmaintainable spaghetti code – a maze of arbitrary jumps without coherent structure. Specifically:
- Jumps ignore standard control flow, confusing logic
- Overlaying tangled logic magnifies complexity exponentially
- Scattered code defies modularity for reuse
- Difficult analysis hinders debugging and profiling
Studies of over 50 million lines of C code found just 3.4% used goto, but those goto usages accounted for 14.6% of all control errors due to complexity. [1]
Bash provides many control flow tools – if-else conditionals, for/while loops, case ranges, functions encapsulating logic, exception handling with trap, etc. These construct readable and maintainable code without arbitrary jumps.
"Goto statements are almost never the best way to implement any control pattern in modern BASH."
By mastering native control flows, goto rarely proves necessary in clean Bash coding. I advocate exhausting these preferred tools before resorting to goto.
Goto Bypasses Orderly Control Flow
Consider this code abusing goto for logic better suited to standard conditionals:
#!/bin/bash
if [ $1 -gt 5 ]; then
goto label
fi
echo "Skip label"
exit 0
label:
echo "Reached label"
This needlessly jumps to label rather than running sequentially. The native if-else approach more clearly conveys the control flow:
if [ $1 -le 5 ]; then
echo "Skip label"
exit 0
else
echo "Reached label"
fi
Prefer native conditionals for inline decision branches – they aid readability through order not possible with jumps.
Goto Creates Spaghetti Logic
Excess jumps also intermingle unrelated logic, tangling control flow. Consider:
#!/bin/bash
goto third
second:
echo "Second"
third:
echo "Third"
first:
echo "First"
goto second
goto first
The arbitrary jumps between disconnected logic greatly increase complexity. Simplify with standard loops:
echo "First"
while true; do
echo "Second"
echo "Third"
done
When overused, goto scatters code, forcing the reader to decode complexity at each jump. Structured loops contain and organize code into reusable logic blocks.
Goto Subverts Functionality and Reuse
Goto also bypasses code modularity. Consider this script with duplicated error handling logic across functions:
#!/bin/bash
connect() {
// connect to resource
if [ $? -ne 0 ]; then
echo "Connection failed"
cleanup
exit 1
fi
}
write_data() {
// write data
if [ $? -ne 0 ]; then
echo "Write failed"
cleanup
exit 1
fi
}
cleanup() {
// cleanup resource
}
connect
write_data
cleanup
Goto could reduce duplication by jumping to centralized cleanup logic but better is moving error handling into its own function:
error_check() {
if [ $? -ne 0 ]; then
echo "Error: $1"
cleanup
exit 1
fi
}
connect() {
// connect
error_check "Connection failed"
}
write_data() {
// write data
error_check "Write failed"
}
cleanup() {
// cleanup
}
Functions enhance reusability. Goto by nature discourages modular design.
Use Cases Where Goto Shines
With those pitfalls noted, some specific use cases remain where goto artfully simplifies control flow:
- Centralized cleanup/rollback handling for errors
- State machines or checkpointing systems
- Breaking out of nested loops or logic flows
- Certain forms of complex validation
- Macro functionality in languages lacking that feature
These patterns share requirements for significant overhead to propagate status across normal structured code or conventions between disjointed components. Goto provides controlled direct jumps outside standard control flow.
Let‘s revisit the state machine example from before using goto:
#!/bin/bash
start() {
state=1
goto begin
}
begin:
case $state in
1)
// do A
state=$(($state + 1))
goto begin
;;
2)
// do B
state=$(($state + 1))
goto begin
;;
3)
goto end
;;
esac
}
end() {
echo "Done"
}
start
The goto loop progresses the state machine through each phase without needing cleanup logic duplication between states. The jump table retains simplicity by defining states in a central routine.
No native Bash feature can implement this checkpoint system as elegantly. By restricting goto usage only where beneficial, we derive advantages without uncontrolled spaghetti code.
Goto Standardizes Error Handling Flows
For robust scripts, consistent error handling proves crucial but oft duplicated. Goto allows centralizing cleanup flows:
#!/bin/bash
on_error() {
echo "Error: $1"
cleanup
exit 1
}
read_config() {
// read
if [ $? -ne 0 ]; then
on_error "Configuration invalid"
fi
}
connect() {
// connect
if [ $? -ne 0 ]; then
on_error "Connection failed"
fi
}
cleanup() {
// cleanup
}
read_config
connect
The on_error function implements standardized error handling, with goto-like flow outside calling contexts. This consolidates handling and rollback in one reusable place.
Goto Breaks Out of Nested Control Flows
Bash lacks direct support for multi-level breaks from nested structures. Goto serves as a "super-break," jumping out arbitrarily.
for x in {1..5}; do
for y in {1..5}; do
if [ $x -eq 3 -a $y -eq 2 ]; then
goto done
fi
done
done
done:
echo "Broke out of nested loop"
This breaks from both nested loops on a matched condition unachievable natively.
Goto Aids Complex Validations
For stateful multi-stage validation code, goto simplifies control flow:
#!/bin/bash
// variables holding state
name_set=0
id_set=0
read -p "Enter name: " name
name_set=1
read -p "Enter ID: " id
id_set=1
if [ $name_set -ne 1 ]; then
goto missing_name
fi
if [ $id_set -ne 1 ]; then
goto missing_id
fi
// passed all checks
register_user $name $id
exit
missing_name:
echo "Error: missing name"
exit 1
missing_id:
echo "Error: missing ID"
exit 1
The goto jumps handle missing values clearly early in execution. Contrast if this required propagating nested conditional state across later logic.
These patterns share a need to break from encapsulation in a controlled mannerAndOverflow status across contexts unavailable natively.
Techniques for Using Goto Well
While goto solves certain problems elegantly, applied haphazardly it still risks confusing logic. Some best practices applying goto judiciously:
- Label judiciously – Only create labels when logic absolutely requires arbitrary jumps. Use native control flow unless unworkable or grossly inefficient.
- Document thoroughly – Comment all goto usage and destinations describing control flow and rationale.
- Scope narrowly – Ideally confine goto usage to local code regions vs globally across files/components. Document scope.
- Encapsulate functionality – Wrap goto statements within functions or code blocks explaining overall component logic flow.
- Adopt conventions– Standardize label prefixes/suffixes indicating goto roles for readability.
- Analyze complexity – Check cyclomatic complexity metrics on code employing goto to highlight risk areas.
The software analysis toolkit Lizard provides several metrics useful in identifying complexity from goto usage:
- Cyclomatic complexity – Measures independent logic paths – code with goto often has high counts.
- NPATH complexity – Totals acyclic execution paths – goto can exponential grow paths.
- Function parameters – Numerous parameters risks confusion. Favor encapsulation.
Monitoring these metrics helps objectively gauge where goto complicates flows. Tame usages will score within reasonable thresholds.
Additionally, graphing tools like CodeCity visualize code structures exposing tangled goto jumps. Remodel to simplify.
Refactoring to Simplify Goto Logic
If goto chains seem knotty, strategically refactoring can yield cleaner designs:
- Break up functions – Long routines with many terminals risk confusion. Partition functionality with one logic path per function.
- Extract validation – Pull complex conditional gotos into validating wrapper functions.
- Create wrappers – Encapsulate messy logic in an explanatory handler function abstracting details.
- Eliminate duplication – Common error logic may indicate removing copy-paste handler code via reuse functions.
Revisit flow complexity after refactoring, simplifying as able with standard approaches before reverting to goto statements.
Expert Opinions on Goto Usage
Given its history crossing programming eras, goto usage spurs no shortage of opinions among professional coders. Recognizing alternative perspectives on goto informs balanced adoption.
Linus Torvalds, creator of Linux
"I don‘t think goto‘s are evil. Sure, they can be – but unlike some other people I don‘t think they are evil in themselves."
Torvalds acknowledges goto naturally risks confusing usage but believes with discipline they solve certain problems cleanly. His Linux development style guide prescribes:
"I think that the goto-hate is pretty much stupid… An occasional goto in Linux is fine."
The key term is occasional. While recognizing goto power judiciously, Linux overall prefers more structured flow control like exceptions for handling errors and early returns over arbitrary jumps.
Steve McConnell, Code Complete author
McConnell‘s classic book on programming best practices identifies several valid use cases for goto aligned with those described previously, including:
- Breaking out of deeply nested structures
- Continuing error processing from multiple points
- Simplifying complex programmed state machines
He cautions to use sparingly, concluding "If you find yourself using goto for any other purposes, treat those situations as candidates for refactoring to simplify the code."
So goto enjoys some specialized use but requires vigilance against overapplying out of habit rather than strict necessity.
Finding Appropriate Goto Balance
Like any tool, goto enables certain solutions – but also risks harm through misuse. Avoiding goto unconditionally ignores its power; embracing goto indiscriminately courts complexity.
The path of mastery threads moderation. Learn structured coding deeply to minimize goto need. But where native options fail or convolute, apply goto judiciously within reason.
Through careful practice, goto transitions from necessary evil to occasional elegant solution in the BASH coder‘s toolkit.


