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.,
test
returns 1 for false, 0 for true. -
Forgetting
set -e
or 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
,-d
in 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
-eq
on non-numeric values fails.
8. Function Syntax Confusion
-
Bash allows two forms:
myfunc() { echo "hi"; }
function myfunc { echo "hi"; } # also valid in Bash
-
return
only sets exit code, not value. Useecho
for 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-specificfunction
keyword 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/bash
vs/bin/sh
changes behavior. -
I/O redirection mistakes:
>
vs>>
, overwriting files unintentionally. -
Using
expr
vs(( ))
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;
if
tests 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?