`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-2145

Array expansion in string arguments may have unexpected resultsSH-2145

Major severityMajor
Bug Risk categoryBug Risk

Interpolating arrays into strings such as "-arg=$@" may not produce the results you expect. If an array is interpolated into a string, the first element will be appended to the preceding part of the string, the last element will be prepended to the rest of the string and the remaining elements in the middle will appear as separate strings. E.g., with an array containing the strings "foo", "bar" and "baz", "--flag=$@" will be expanded into the three arguments "--flag=foo" "bar" "baz".

  • If the intention is to concatenate all the array elements into one argument, use $*. This concatenates based on IFS.
  • If the intention is to provide each array element as a separate argument, the array expansion should be in a separate argument for clarity's sake.

Bad Practice

# 1
printf "Error: %s\n" "Bad parameters: $@"

# 2
printf "Error: %s\n" "Bad parameters: ${ARRAY_VAR[@]}"
# 1
printf "Error: %s\n" "Bad parameters: $*"

# 2
printf "Error: %s\n" "Bad parameters: " "${ARRAY_VAR[@]}"

Exceptions

The POSIX specified behavior of $@ (and by extension arrays) when interpolated into strings is often unexpected:

if the parameter being expanded was embedded within a word, the first field shall be joined with the beginning part of the original word and the last field shall be joined with the end part of the original word. In all other contexts the results of the expansion are unspecified. If there are no positional parameters, the expansion of '@' shall generate zero fields, even when '@' is within double-quotes; however, if the expansion is embedded within a word which contains one or more other parts that expand to a quoted null string, these null string(s) shall still produce an empty field, except that if the other parts are all within the same double-quotes as the '@', it is unspecified whether the result is zero fields or one empty field.

If you're aware of this behavior and intend to take advantage of it, you can ignore this warning. However, you can also usually rewrite the offending code into a less surprising form. For example, here's a wrapper script that uses this behavior to substitute certain commands by defining a function for them:

#!/bin/sh
fixed_fgrep() { grep -F "$@"; }
fixed_echo() { printf '%s\n' "$*"; }
fixed_seq() { echo "seq is not portable" >&2; return 1; }

if command -v "fixed_$1" > /dev/null 2>&1
then
    # shellcheck disable=SC2145   # I know how fixed_"$@" behaves and it's correct!
    fixed_"$@"
else
    "$@"
fi

Here's the same script without relying on this behavior:

#!/bin/sh
fixed_fgrep() { grep -F "$@"; }
fixed_echo() { printf '%s\n' "$*"; }
fixed_seq() { echo "seq is not portable" >&2; return 1; }

cmd="$1"
shift

if command -v "fixed_$cmd" > /dev/null 2>&1
then
    # Perhaps more straight forward with fewer surprises:
    "fixed_$cmd" "$@"
else
    "$cmd" "$@"
fi