`for` loop used on `find` outputSH-2044Opening and closing brackets do not matchSH-1033Missing `fi` statement hereSH-1047Missing `}`SH-1056Missing `done` statementSH-1061Missing `then` statementSH-1050Semicolon detected after `else`SH-1053`[ .. ]` is not a part of shell syntaxSH-1014No `fi` found for the `if` statementSH-1046Wrong way of declaring parametersSH-1065Missing `EOF` tokenSH-1044Unescaped `&` will end the command hereSH-1132Consider using `elif` instead of `if`SH-1075File has UTF-8 BOMSH-1082Unexpected line-startSH-1133Terminator used after here documentSH-1121Avoid using literal tilde in PATHSH-2147Empty `then` clause detectedSH-1048Missing whitespaceSH-1020Semicolon detected after `then`SH-1051Missing space after `{`SH-1054Found 'eof' further down, but not on a separate lineSH-1041Non-alphanumeric names may have been skippedSH-2038Found a comment after here-doc tokenSH-1120Consider using command substitution instead of `'...'`SH-2041Avoid use of `ls | grep`SH-2010Consider using command substitution to assign the output of a pipelineSH-2036Missing space after `$(`SH-1102Leading spaces detected befor shebangSH-1114Missing command substitutionSH-2037Iterating over a constant valueSH-2043Consider using glob to iterate over `ls` outputSH-2045Consider using a `( subshell )` to avoid having to `cd` backSH-2103Functions must not return multiple valuesSH-2151Array expansion in string arguments may have unexpected resultsSH-2145Evaluation order of arguments to `find` might be different than expectedSH-2146Use `-exec sh -c` to invoke a shell with `find`SH-2150Syntax errorSH-1070`(( ))` doesn't support decimalsSH-2079Missing space after `<`SH-1038`$` used on the left side of an assignmentSH-1066Whitespace found after `=`SH-1007Apostrophe terminates the single quoted stringSH-1011Missing `then` for the `if` statementSH-1049Unary test operator not followed by a valid shell wordSH-1019Missing `do` statementSH-1058Unescaped `$` used in a literal contextSH-1000Consider using `find` instead of `ls` to better handle non-alphanumeric filenamesSH-2012Lexicographical comparison operator used for numerical comparisonSH-2071Escape `\<` to prevent it redirecting (or switch to `[[ .. ]]`)SH-2073Mismatched keywordSH-1089Invalid use of parenthesesSH-1088C language style commentSH-1127`-n` doesn't work with unquoted argumentsSH-2070Unexpected character after terminating `]`SH-1136Consider using `pgrep` instead of grepping `ps` outputSH-2009Shell functions passed to an external commandSH-2033Consider using `&&` instead of `-a` in `[[..]]`SH-2108use of an invalid operatorSH-2122Found word outside quotesSH-2026Unknown unary operatorSH-2058Keyword is not in lower caseSH-1081Loop will run only once because there's no word splittingSH-2066Double quote array expansions to avoid re-splitting elementsSH-2068Unnecessary use of `$` / `${}` with arithmetic variableSH-2004Shebang with more than one parameterSH-2096Missing `$`SH-1116shebang is not the first lineSH-1128Quote 'EOF' to make here document expansions happen on the server side rather than on the clientSH-2087Bash doesn't allow variables in brace range expansionsSH-2051`[ .. ]` can't match globsSH-2081Unexpected `==`SH-1097Variable modified locally due to `pipe`SH-2030Command substitutions will run *before* the enclosing command instead of within itSH-2014String not closedSH-1078`=~` is not allowed in `[ ]`SH-2074`>` behaves like a redirection operator hereSH-2065Unknown binary operatorSH-2057Shell functions passed to `su`SH-2032Missing newline between the end token and terminating `)`SH-1119`||` is not supported inside `[ .. ]`SH-2109`break` used outside of a loopSH-2105Use `[ a ] && [ b ]` instead of `[ a && b ]`SH-2107Invalid `-o` in `[[..]]`SH-2110A system directory is being deletedSH-2114Aliases can't use positional parametersSH-2142Consider using a `for` loop instead of `-e`SH-2144Variables must be defined before useSH-2154Missing space before `#`SH-1099Use double quote to prevent globbing and word splittingSH-2086Unquoted HTML entity foundSH-1109Unicode double quotes detectedSH-1015Can't escape `(` or `)` inside `[[..]]`SH-1029`eof` is not on a separate lineSH-1042Single quote not escaped properlySH-1003Braces are required for positionals over 9SH-1037Unicode single quote usedSH-1016Invalid or unescaped `(` foundSH-1036Script uses carriage return ` `SH-1017Function definition missing bodySH-1064Unnecessary use of multiple line terminatorsSH-1045Assigning to a dynamically created variableSH-1067Missing space before `[`SH-1069backslash / linefeed is a literal where it is usedSH-1004Detected a literal where `tab`, `linefeed` or `cariage return` might be expectedSH-1012Escape `(` and `)` or preferably combine `[..]` expressionsSH-1028Remove indentation before end tokenSH-1039Use `( .. )` to group conditionsSH-1026Unicode non-breaking space foundSH-1018Use tabs for indentationSH-1040Detected spaces around `=` operatorSH-1068Use semicolon or linefeed before `done`SH-1010Missing whitespaceSH-1035Whitespace detected between `#` and `!` in the `shebang`SH-1115Deprecated `$[..]` usedSH-2007Unicode quote detectedSH-1110Missing `!` in shebangSH-1113Executing output of a commandSH-2091Unused variableSH-2034Useless `cat`SH-2002Consider escaping `$` to make it a literalSH-1135Consider using `$HOME` instead of tilde `~` to expand in quotesSH-2088Whitespace detected after the here-doc end tokenSH-1118Consider using braces to expand arraysSH-1087`tr` used to replace wordsSH-2020Remove `exec` if script should continue after this commandSH-2093Consider escaping special characters when using `eval`SH-1098Consider using `$((..))` for arithmetic operationsSH-2100Increase precision by replacing `a/b*c` with `a*c/b`SH-2017Use `[:upper:]` to support accents and foreign alphabetsSH-2019Missing space or linefeed between the function name and bodySH-1095Missing space before `!`SH-1129Consider reformattingSH-1079`echo` doesn't read from stdinSH-2008`echo` used to expand escape sequencesSH-2028Trailing spaces after `\`SH-1101Named class needs an outer `[]`SH-2101Verify use of the pipeline operatorSH-2106Consider using `func()`SH-2113Brace expansions/globs used in an assignmentSH-2125Use `=` for non-integer comparisonsSH-2130Shell search path `PATH` is being overriddenSH-2123Consider using `grep -q`SH-2143`=~` used with a globSH-2049Use spaces to separate array elementsSH-2054Consider using `while read` to read linesSH-2013Quote parameter to prevent glob expansionSH-2060Quote the passed parameter to prevent the shell from interpreting itSH-2061To redirect both `stdout` and `stderr`, `2>&1` must be last (or use `'{ cmd > file; } 2>&1'` to clarify)SH-2069Possibly missing `$`SH-2078Missing space around `=` operatorSH-1108Unnecessary use of `echo`SH-2005Remove backticks to avoid executing outputSH-2092Glob used with `grep`SH-2063Remove '$' or use '_=$((expr))' to avoid executing outputSH-2084`sudo` doesn't affect redirectsSH-2024Use `"$@"` to prevent whitespace problemsSH-2048`{` `/` `}` is a literal hereSH-1083Command swallows `stdin`SH-2095Unicode dash detectedSH-1100`$` used on the iterator name in for loopsSH-1086Unsupported decimalsSH-2072Missing space before `:`SH-1130`A && B || C` is not equivalent to `if A then B else C`SH-2015Missing `;` or `+` terminating `-exec`SH-2067Use `$((..))` for arithmetic operationSH-2099Consider using `find` for better handling non-alphanumeric filenamesSH-2011Use of variable in `printf` format stringSH-2059Unescaped enclosed quotesSH-2027Numbers with a leading `0` are considered octalSH-2080Use double quotes to expand expressionsSH-2016Quote the RHS of the operator to prevent glob matchingSH-2053Quoting RHS of `=~` will match literally rather than as a regexSH-2076Use of legacy backtickSH-2006Consider using `./` or `--` globSH-2035Enclose escape sequences in `\[..\]` to prevent line wrapping issuesSH-2025Consider using `${variable//search/replace}`SH-2001`trap` code will expand during definitionSH-2064Missing `#` in the shebang lineSH-1104Use `#!` for shebangSH-1084Comparison with literalSH-2050Unescaped argument foundSH-2029Text added after here-doc terminating tokenSH-1122Use backtick for command expansionSH-1077Variable modified in the subshellSH-2031To expand via indirection, use name="foo$n"; echo "${!name}"SH-2082Assignment visible only to the forked processSH-2097Encountered use of `[]` around ranges in `tr`SH-2021Missing spaces around the comparison operatorSH-2077Quote `grep` patterns to stop shells from interpreting themSH-2062Consider quoting command expansion to prevent word splittingSH-2046Make sure not to read and write the same file in the same pipelineSH-2094Use `[:lower:]` to support accents and foreign alphabetsSH-2018Use `elif` to start another branchSH-1131Referred argument is never passedSH-2120Do not use `set` to assign a variableSH-2121Ranges can only match single charactersSH-2102Consider using `return`SH-2104Use `"${var:?}"` to ensure this never expands to `/*`SH-2115Unnecessary use of `echo`SH-2116Use `su -c` or `sudo` to run command as another userSH-2117An array is being assigned to a stringSH-2124Consider escaping the expansionSH-2139Unquoted literal string detected between two double quoted stringsSH-2140Potentially misspelled variable name foundSH-2153Avoid using `$` with numeric or string indicesSH-2149Function returns something other than a number in the range of 0-255SH-2152Escaping a non-special characterSH-1001Outdated `expr` statementSH-2003Literal quote/backslash detected in the argument stringSH-2089Consider using single quotesSH-2141Consider using `${#variable}`SH-2000Consider grouping multiple command redirectsSH-2129Consider using `grep -c`SH-2126
Shell logoShell/
SH-2044

`for` loop used on `find` outputSH-2044

Critical severityCritical
Bug Risk categoryBug Risk

The usage of for loops over find output is fragile because it relies on word splitting and will evaluate globs. This will fail for filenames containing spaces, globbing, or regex characters and similar, such as My File.mp3, MyFile[2008].mp3. This also has a series of potential globbing issues depending on other filenames in the directory. For example: if you have MyFile2.mp3 and MyFile[2014].mp3, the former file will play twice and the latter will not play at all (because [2014] behaves as regex here, and MyFile2.mp3 will match this). find -exec for i in glob and find + while do not rely on word splitting, so it is recommended to use these instead.

Problematic code:

for file in $(find mydir -mtime -7 -name '*.mp3')
do
  let count++
  echo "Playing file no. $count"
  play "$file"
done
echo "Played $count files"

Preferred code:

There are many possible fixes, each with its pros and cons.

The most general fix (that requires the least amount of thinking to apply) is having find output a \0 separated list of files and consuming them in a while read loop:

while IFS= read -r -d '' file
do
  let count++
  echo "Playing file no. $count"
  play "$file"
done <   <(find mydir -mtime -7 -name '*.mp3' -print0)
echo "Played $count files"

In usage it's very similar to the for loop: it gets its output from a find statement, executes a shell script body, allows updating/aggregating variables, and the variables are available when the loop ends.

Please note: this requires Bash, and works with the GNU, Busybox, OS X, FreeBSD and OpenBSD versions of find, but not the POSIX version.

If find is just matching globs recursively If you don't need find logic like -mtime -7 and just use it to match globs recursively (all *.mp3 files under a directory), you can instead use globstar and nullglob instead of find, and still use a for loop:

shopt -s globstar nullglob
for file in mydir/**/*.mp3
do
  let count++
  echo "Playing file no. $count"
  play "$file"
done
echo "Played $count files"

Note: This is bash 4 specific.

For POSIX If you need POSIX compliance, this is a fair approach:

find mydir ! -name "$(printf "*\n*")" -name '*.mp3' > tmp
while IFS= read -r file
do
  let count++
  echo "Playing file #$count"
  play "$file"
done < tmp
rm tmp
echo "Played $count files"

The only problem is for filenames containing line feeds. A ! -name "$(printf "*\n*")" has been added to simply skip these files, just in case there are any.

If you don't need variables to be available after the loop (here, if you don't need to print the final play count at the end), you can skip the tmp file and just pipe from find to while.

For simple commands with no aggregation If you don't need a shell script loop body or any form of variable like if you only wanted to play the file, you can dramatically simplify while maintaining POSIX compatibility:

# Simple and POSIX
find mydir -name '*.mp3' -exec play {} \;

This does not allow things like let counter++ because let is a shell builtin, not an external command.

For shell commands with no aggregation If you do need a shell script body but no aggregation, you can do the above by invoking sh (this is still POSIX compliant):

find mydir -name '*.mp3' -exec sh -c '
    echo "Playing ${1%.mp3}"
    play "$1"
  ' sh {} \;

This would not be possible without sh, because ${1%.mp3} is a shell construct that find can't evaluate by itself. If we had tried to use let counter++ in this loop, we would have found that the value never changes since the let part would run in an independent subshell.

Note that using + instead of \;, and using an embedded for file in "$@" loop rather than "$1", will not allow aggregating variables. This is because for large lists, find will invoke the command multiple times, each time with some chunk of the input.

Exception:

If you are familiar with this and carefully apply IFS=$'\n' and set -f, you can ignore this issue.