Avoid 40 Common Bash Pitfalls: Best Practices for Safe Shell Scripting
This article compiles more than 40 everyday Bash programming mistakes that both beginners and veterans often overlook, explains why each example is wrong, and provides clear, corrected alternatives along with references to deeper resources, helping readers write more reliable shell scripts.
1. for i in $(ls *.mp3)
Bash loops often misuse command substitution without quoting, causing word splitting and unintended filename expansion.
for i in $(ls *.mp3); do
# WRONG!
some command $i # WRONG!
done
for i in $(ls); # WRONG!
for i in `ls`; # WRONG!
for i in $(find . -type f); # WRONG!
for i in `find . -type f`; # WRONG!
files=($(find . -type f)) # WRONG!
for i in ${files[@]}; # WRONG!Unquoted command substitution splits results on IFS.
Parsing ls output is fragile and should be avoided.
When filenames contain spaces, word splitting breaks them into separate words. Use globbing directly:
for i in *.mp3; do
echo "$i"
doneCheck for empty globs to avoid unintended single iteration:
# POSIX
for i in *.mp3; do
[ -e "$i" ] || continue
some command "$i"
done2. cp $file $target
Always quote variables to handle spaces or special characters, and use "--" to stop option parsing when filenames start with a dash.
cp -- "$file" "$target"3. Filenames starting with '-'
Prefix commands with "--" or use relative paths (./) to prevent treating filenames as options.
cp -- "$file" "$target"
# or
for i in ./*.mp3; do
cp "$i" /target
done4. [ $foo = "bar" ]
Quote variable expansions inside test brackets to avoid errors when the variable is empty or contains spaces.
# POSIX
[ "$foo" = bar ]5. cd $(dirname "$f")
Quote the command substitution to prevent word splitting and filename expansion.
cd "$(dirname "$f")"6. [ "$foo" = bar && "$bar" = foo ]
Combine multiple tests with separate [ ] commands or use [[ ]] for clearer syntax.
[ "$foo" = bar ] && [ "$bar" = foo ] # POSIX
[[ $foo == bar && $bar == foo ]] # Bash/Ksh7. [[ $foo > 7 ]]
Use arithmetic evaluation (( )) for numeric comparisons instead of [[ ]].
8. grep foo bar | while read -r; do ((count++)); done
The loop runs in a subshell, so variable changes are lost. Use shopt -s lastpipe in Bash ≥4.2 or restructure the code.
9. if [grep foo myfile]
"[" is a command, not part of if syntax. Use the command directly:
if grep -q foo myfile; then
...
fi10. if [bar="$foo"]; then ...
Spaces are required around "=" inside test brackets.
11. if [[ a = b && c = d ]]; then ...
Separate conditions with && outside the [[ ]] or use multiple [[ ]] expressions.
12. read $foo
Read into a variable without the leading $. Use read foo or IFS= read -r foo for safety.
13. cat file | sed s/foo/bar/ > file
Never read and write the same file in a pipeline. Use a temporary file or sed -i (GNU sed ≥4).
14. echo $foo
Unquoted variables undergo word splitting and filename expansion. Use printf "%s\n" "$foo" for reliable output.
15. $foo=bar
Assignment syntax does not allow spaces around the equals sign.
16. foo = bar
Spaces cause the shell to treat foo as a command. Correct form: foo=bar or foo="bar".
17. echo <<EOF
Here‑documents require a command that reads stdin (e.g., cat <<EOF) or use quoted multi‑line strings.
18. su -c 'some command'
On some systems -c specifies a login class. Provide the username explicitly: su root -c 'some command'.
19. cd /foo; bar
Check the exit status of cd before running subsequent commands: cd /foo && bar or use command grouping with { ...; }.
20. [ bar == "$foo" ]
Use = for string equality inside [ ] and quote the right‑hand side.
21. for i in {1..10}; do ./something &; done
Remove the stray semicolon after & or write the loop on multiple lines.
22. cmd1 && cmd2 || cmd3
Be aware that || runs when the preceding command returns a non‑zero status, which may not match the intended if…else logic.
23. echo "Hello World!"
In interactive Bash, ! triggers history expansion. Disable it with set +H or use single quotes.
24. for arg in $*
$*and $@ expand to the entire argument list. Use for arg in "$@" or simply for arg; do …; done.
25. function foo()
Portable function definition: foo() { …; }.
26. echo "~"
Tilde expansion occurs only when ~ is unquoted. Use $HOME for a quoted path.
27. local varname=$(command)
Separate the local declaration from the command substitution to preserve the command’s exit status.
28. export foo=~/bar
Exporting with assignment may not expand ~ in all shells. Assign first, then export.
29. sed 's/$foo/good bye/'
Single quotes prevent variable expansion; use double quotes and escape as needed.
30. tr [A-Z] [a-z]
Bracket expressions are treated as glob patterns. Use tr A-Z a-z or locale‑aware character classes.
31. ps ax | grep gedit
Filter out the grep process itself with grep -v grep or grep [g]edit.
32. printf "$foo"
Unquoted format strings can misinterpret % or backslashes. Use printf "%s\n" "$foo".
33. for i in {1..$n}
Brace expansion does not evaluate variables. Use a C‑style loop: for ((i=1; i<=n; i++)); do …; done.
34. if [[ $foo = $bar ]]
Without quotes, the right‑hand side is treated as a pattern. Quote it for literal comparison.
35. if [[ $foo =~ 'some RE' ]]
Quotes suppress regex interpretation. Store the regex in a variable or omit the quotes.
36. [ -n $foo ] or [ -z $foo ]
Always quote the variable to avoid errors with empty or spaced values.
37. [[ -e "$broken_symlink" ]] returns 1
Use -e or -L to test for broken symlinks.
38. ed file <<<"g/d{0,3}/s//e/g" fails
Brace quantifiers must be escaped; modern editors are preferred.
39. expr sub-string fails for "match"
Prefix the command with + to ignore the special keyword match, or use Bash parameter expansion.
40. On UTF-8 and Byte‑Order Marks (BOM)
UTF‑8 files generally should not contain a BOM; scripts may misinterpret it as characters.
41. content=$( Command substitution strips trailing newlines. 42. somecmd 2>&1 >>logfile Redirections are parsed left‑to‑right; redirect stdout first, then stderr: somecmd >>logfile 2>&1 . 43. cmd; (( ! $? )) || die Prefer explicit if statements or case for error handling. References: [1] Bash Pitfalls – http://mywiki.wooledge.org/BashPitfalls [2] Do not parse ls – http://mywiki.wooledge.org/ParsingLs [3] Word Splitting – http://mywiki.wooledge.org/WordSplitting [4] Globbing – http://mywiki.wooledge.org/glob [5] Using Find – http://mywiki.wooledge.org/UsingFind [6] Quotes – http://mywiki.wooledge.org/Quotes [7] Word Splitting – http://mywiki.wooledge.org/WordSplitting [8] locale – http://mywiki.wooledge.org/locale [9] Bash FAQ #24 – http://mywiki.wooledge.org/BashFAQ/024 [10] test command – http://mywiki.wooledge.org/BashFAQ/031 [11] Subshell – http://mywiki.wooledge.org/SubShell [12] Bash FAQ #24 – http://mywiki.wooledge.org/BashFAQ/024 [13] Word Splitting – http://mywiki.wooledge.org/WordSplitting [14] Globbing – http://mywiki.wooledge.org/glob [15] Here Document – http://www.tldp.org/LDP/abs/html/here-docs.html [16] su -c login‑class – http://www.openbsd.org/cgi-bin/man.cgi?query=su&sektion=1 [17] Command grouping – http://mywiki.wooledge.org/BashGuide/CompoundCommands [18] Exclamation mark escaping – https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html [19] Tilde expansion – https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html [20] local – http://tldp.org/LDP/abs/html/localvar.html [21] Command substitution – http://mywiki.wooledge.org/CommandSubstitution [22] Quotes – http://mywiki.wooledge.org/Quotes [23] locale – http://mywiki.wooledge.org/locale [24] Process Management – http://mywiki.wooledge.org/ProcessManagement [25] Bash parser – http://mywiki.wooledge.org/BashParser [26] Brace expansion – http://mywiki.wooledge.org/BraceExpansion [27] Shell numeric sequences – https://kodango.com/generate-number-sequence-in-shell [28] Parameter expansion – http://mywiki.wooledge.org/BashFAQ/073 [29] Redirection – http://wiki.bash-hackers.org/howto/redirection_tutorial [30] BashFAQ – http://mywiki.wooledge.org/BashPitfalls#cat_file_.7C_sed_s.2Ffoo.2Fbar.2F_.3E_file
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
