Absolutely — Bash feels strange because it blends shell commands, programming constructs, and POSIX quirks. If you come from Python, it’s easy to trip over hidden pitfalls. Here’s a thorough list, organized by category:
1. Quoting Issues
-
Unquoted variables can lead to word splitting and glob expansion:
name="John Doe"
echo $name # BAD: expands to two words
echo "$name" # GOOD: preserves spaces
-
Forgetting quotes can break tests or loops:
if [ $a -eq 5 ]; then ... # ERROR if $a is empty
if [ "$a" -eq 5 ]; then ... # SAFE
-
Using single quotes
'prevents variable expansion:
echo '$HOME' # prints literally $HOME
echo "$HOME" # prints /home/user
2. Conditional Confusion
-
Mixing
[ ],[[ ]],(( )), and()incorrectly:
if (("$a" < 5)); then ... # BAD: use ((a < 5))
if [ $a < 5 ]; then ... # BAD: < is string comparison in [ ]
-
Confusing string vs numeric comparisons:
[ "$a" -lt 5 ] # numeric
[ "$a" \< "5" ] # string (note escaped <)
-
Assuming
if [ "$a" ]checks for number. It actually checks non-empty string.
3. Exit Status Pitfalls
-
Bash uses exit codes: 0 = success, non-zero = failure. Python uses
True/False.
if grep foo file.txt; thenecho "Found"
fi
-
Many commands return unexpected codes; e.g.,
testreturns 1 for false, 0 for true. -
Forgetting
set -eor error handling can silently ignore failures.
4. Loop and Variable Scope Issues
-
Variables are global by default; subshells create separate scopes:
( a=5 )
echo $a # empty: set in subshell
-
Iterating over unquoted expansions can split on spaces unexpectedly:
files="file 1.txt file2.txt"
for f in $files; do echo "$f"; done
# BAD: splits "file 1.txt" into "file" and "1.txt"
for f in "$files"; do echo "$f"; done # prints entire string
-
Use arrays to handle multiple items safely.
5. Command Substitution Oddities
-
$(command)is preferred over backticks`command`. -
Spaces and newlines in output can break things:
files=$(ls)
# BAD if filenames have spaces
-
Better: use arrays:
mapfile -t files < <(ls)
6. File and Path Problems
-
Forgetting
-e,-f,-din tests leads to errors:
if [ -f "$file" ]; then echo "File exists"; fi
-
Globbing (
*,?) can expand unexpectedly; always quote orshopt -s nullglob.
7. Arithmetic Gotchas
-
(( ... ))handles integers only. No floating point:
a=3.14
((a > 3)) # ERROR
-
Bash string variables are always strings; using
-eqon non-numeric values fails.
8. Function Syntax Confusion
-
Bash allows two forms:
myfunc() { echo "hi"; }
function myfunc { echo "hi"; } # also valid in Bash
-
returnonly sets exit code, not value. Useechofor output.
9. Quirky String Manipulation
-
${var#pattern},${var%pattern}— powerful, but not obvious. -
${var:-default}vs${var:=default}— subtle difference between default for expansion and assignment.
10. Portability Pitfalls
-
Scripts using
[[ ]],(( )), or Bash-specificfunctionkeyword are not POSIX compliant. -
Some commands (e.g.,
read -p) differ between Bash, Dash, and macOS.
11. Miscellaneous Gotchas
-
&&/||short-circuit can silently skip commands. -
Forgetting
#!/bin/bashvs/bin/shchanges behavior. -
I/O redirection mistakes:
>vs>>, overwriting files unintentionally. -
Using
exprvs(( ))vs$(( ))— inconsistent arithmetic can confuse beginners.
✅ Advice for Pythonistas:
-
Quote everything unless you deliberately want word splitting.
-
Use
[[ ]]for string/numeric conditions in Bash scripts. -
Use arrays to store multiple items safely.
-
Check exit codes carefully;
iftests commands, not expressions. -
Enable strict mode at the top of scripts:
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
-
-e→ exit on error -
-u→ error on undefined variables -
-o pipefail→ pipe errors propagate
If you want, I can make a “cheat sheet of safe Bash practices for Python programmers” — basically a guide to avoid 90% of these pitfalls. It’s a huge time saver.
Do you want me to do that?
