The loop that backed up nothing for three weeks
A nightly job looped for f in $(ls /data/exports) and copied each file to a backup volume. It ran green every night. Three weeks later we needed the Q3 final.xlsx export and it was not in the backup — not the current one, not any of the twenty-one before it. The loop had been splitting Q3 final.xlsx into Q3 and final.xlsx, trying to copy two paths that did not exist, logging two harmless "no such file" lines, and moving on. The files without spaces backed up fine, which is exactly why nobody caught it.
The bug was not cp. It was looping over parsed command output instead of a glob.
Loop a glob — the form for files
for f in "$SRC_DIR"/*.xlsx lets bash expand the glob itself, which produces a correctly-separated list no matter what is in the names. Quoting "$f" on use is the other half — the split that bit us happens when the variable is used, not only in the loop header. The -- tells cp to stop reading options, so a file named -rf cannot turn into a flag.
Loop a range or a counter — the form for numbers
{1..5} is fixed before variables expand, so {1..$attempts} produces the literal string {1..5}-style garbage, not a range. The moment your bound is a variable, the C-style for (( ... )) is the correct tool.
Loop an array — quote the expansion
"${servers[@]}" with quotes and [@] keeps db primary as one element. Drop the quotes and you are back to word-splitting — the same bug, new disguise.
The rule across all of these: glob over parse, quote on use. For reading a file's lines one at a time, a for loop is the wrong tool — see read a file line by line. And wrap any loop that touches real files in set -euo pipefail so a failure stops the script instead of looping past it.