Showing 270 of 270 total issues
Double quote to prevent globbing and word splitting. Open
--ignore-table=$DB_NAME.cataloginventory_stock_status_idx \
- Read upRead up
- Exclude checks
Double quote to prevent globbing and word splitting.
Problematic code:
echo $1
for i in $*; do :; done # this done and the next one also applies to expanding arrays.
for i in $@; do :; done
Correct code:
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'
Rationale
The first code looks like "print the first argument". It's actually "Split the first argument by IFS (spaces, tabs and line feeds). Expand each of them as if it was a glob. Join all the resulting strings and filenames with spaces. Print the result."
The second one looks like "iterate through all arguments". It's actually "join all the arguments by the first character of IFS (space), split them by IFS and expand each of them as globs, and iterate on the resulting list". The third one skips the joining part.
Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.
Strictly speaking, only expansions themselves need to be quoted, but for stylistic reasons, entire arguments with multiple variable and literal parts are often quoted as one:
$HOME/$dir/dist/bin/$file # Unquoted (bad)
"$HOME"/"$dir"/dist/bin/"$file" # Minimal quoting (good)
"$HOME/$dir/dist/bin/$file" # Canonical quoting (good)
When quoting composite arguments, make sure to exclude globs and brace expansions, which lose their special meaning in double quotes: "$HOME/$dir/src/*.c"
will not expand, but "$HOME/$dir/src"/*.c
will.
Note that $( )
starts a new context, and variables in it have to be quoted independently:
echo "This $variable is quoted $(but this $variable is not)"
echo "This $variable is quoted $(and now this "$variable" is too)"
Exceptions
Sometimes you want to split on spaces, like when building a command line:
options="-j 5 -B"
make $options file
Just quoting this doesn't work. Instead, you should have used an array (bash, ksh, zsh):
options=(-j 5 -B) # ksh: set -A options -- -j 5 -B
make "${options[@]}" file
or a function (POSIX):
make_with_flags() { make -j 5 -B "$@"; }
make_with_flags file
To split on spaces but not perform glob expansion, Posix has a set -f
to disable globbing. You can disable word splitting by setting IFS=''
.
Similarly, you might want an optional argument:
debug=""
[[ $1 == "--trace-commands" ]] && debug="-x"
bash $debug script
Quoting this doesn't work, since in the default case, "$debug"
would expand to one empty argument while $debug
would expand into zero arguments. In this case, you can use an array with zero or one elements as outlined above, or you can use an unquoted expansion with an alternate value:
debug=""
[[ $1 == "--trace-commands" ]] && debug="yes"
bash ${debug:+"-x"} script
This is better than an unquoted value because the alternative value can be properly quoted, e.g. wget ${output:+ -o "$output"}
.
As always, this warning can be [[ignore]]d on a case-by-case basis.
this is especially relevant when BASH many not be available for the array work around. For example, use in eval or in command options where script has total control of the variables...
FLAGS="-av -e 'ssh -x' --delete --delete-excluded"
...
# shellcheck disable=SC2086
eval rsync $FLAGS ~/dir remote_host:dir
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Double quote to prevent globbing and word splitting. Open
--ignore-table=$DB_NAME.catalog_product_index_price_opt_agr_tmp \
- Read upRead up
- Exclude checks
Double quote to prevent globbing and word splitting.
Problematic code:
echo $1
for i in $*; do :; done # this done and the next one also applies to expanding arrays.
for i in $@; do :; done
Correct code:
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'
Rationale
The first code looks like "print the first argument". It's actually "Split the first argument by IFS (spaces, tabs and line feeds). Expand each of them as if it was a glob. Join all the resulting strings and filenames with spaces. Print the result."
The second one looks like "iterate through all arguments". It's actually "join all the arguments by the first character of IFS (space), split them by IFS and expand each of them as globs, and iterate on the resulting list". The third one skips the joining part.
Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.
Strictly speaking, only expansions themselves need to be quoted, but for stylistic reasons, entire arguments with multiple variable and literal parts are often quoted as one:
$HOME/$dir/dist/bin/$file # Unquoted (bad)
"$HOME"/"$dir"/dist/bin/"$file" # Minimal quoting (good)
"$HOME/$dir/dist/bin/$file" # Canonical quoting (good)
When quoting composite arguments, make sure to exclude globs and brace expansions, which lose their special meaning in double quotes: "$HOME/$dir/src/*.c"
will not expand, but "$HOME/$dir/src"/*.c
will.
Note that $( )
starts a new context, and variables in it have to be quoted independently:
echo "This $variable is quoted $(but this $variable is not)"
echo "This $variable is quoted $(and now this "$variable" is too)"
Exceptions
Sometimes you want to split on spaces, like when building a command line:
options="-j 5 -B"
make $options file
Just quoting this doesn't work. Instead, you should have used an array (bash, ksh, zsh):
options=(-j 5 -B) # ksh: set -A options -- -j 5 -B
make "${options[@]}" file
or a function (POSIX):
make_with_flags() { make -j 5 -B "$@"; }
make_with_flags file
To split on spaces but not perform glob expansion, Posix has a set -f
to disable globbing. You can disable word splitting by setting IFS=''
.
Similarly, you might want an optional argument:
debug=""
[[ $1 == "--trace-commands" ]] && debug="-x"
bash $debug script
Quoting this doesn't work, since in the default case, "$debug"
would expand to one empty argument while $debug
would expand into zero arguments. In this case, you can use an array with zero or one elements as outlined above, or you can use an unquoted expansion with an alternate value:
debug=""
[[ $1 == "--trace-commands" ]] && debug="yes"
bash ${debug:+"-x"} script
This is better than an unquoted value because the alternative value can be properly quoted, e.g. wget ${output:+ -o "$output"}
.
As always, this warning can be [[ignore]]d on a case-by-case basis.
this is especially relevant when BASH many not be available for the array work around. For example, use in eval or in command options where script has total control of the variables...
FLAGS="-av -e 'ssh -x' --delete --delete-excluded"
...
# shellcheck disable=SC2086
eval rsync $FLAGS ~/dir remote_host:dir
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Double quote to prevent globbing and word splitting. Open
--ignore-table=$DB_NAME.cataloginventory_stock_status_tmp \
- Read upRead up
- Exclude checks
Double quote to prevent globbing and word splitting.
Problematic code:
echo $1
for i in $*; do :; done # this done and the next one also applies to expanding arrays.
for i in $@; do :; done
Correct code:
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'
Rationale
The first code looks like "print the first argument". It's actually "Split the first argument by IFS (spaces, tabs and line feeds). Expand each of them as if it was a glob. Join all the resulting strings and filenames with spaces. Print the result."
The second one looks like "iterate through all arguments". It's actually "join all the arguments by the first character of IFS (space), split them by IFS and expand each of them as globs, and iterate on the resulting list". The third one skips the joining part.
Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.
Strictly speaking, only expansions themselves need to be quoted, but for stylistic reasons, entire arguments with multiple variable and literal parts are often quoted as one:
$HOME/$dir/dist/bin/$file # Unquoted (bad)
"$HOME"/"$dir"/dist/bin/"$file" # Minimal quoting (good)
"$HOME/$dir/dist/bin/$file" # Canonical quoting (good)
When quoting composite arguments, make sure to exclude globs and brace expansions, which lose their special meaning in double quotes: "$HOME/$dir/src/*.c"
will not expand, but "$HOME/$dir/src"/*.c
will.
Note that $( )
starts a new context, and variables in it have to be quoted independently:
echo "This $variable is quoted $(but this $variable is not)"
echo "This $variable is quoted $(and now this "$variable" is too)"
Exceptions
Sometimes you want to split on spaces, like when building a command line:
options="-j 5 -B"
make $options file
Just quoting this doesn't work. Instead, you should have used an array (bash, ksh, zsh):
options=(-j 5 -B) # ksh: set -A options -- -j 5 -B
make "${options[@]}" file
or a function (POSIX):
make_with_flags() { make -j 5 -B "$@"; }
make_with_flags file
To split on spaces but not perform glob expansion, Posix has a set -f
to disable globbing. You can disable word splitting by setting IFS=''
.
Similarly, you might want an optional argument:
debug=""
[[ $1 == "--trace-commands" ]] && debug="-x"
bash $debug script
Quoting this doesn't work, since in the default case, "$debug"
would expand to one empty argument while $debug
would expand into zero arguments. In this case, you can use an array with zero or one elements as outlined above, or you can use an unquoted expansion with an alternate value:
debug=""
[[ $1 == "--trace-commands" ]] && debug="yes"
bash ${debug:+"-x"} script
This is better than an unquoted value because the alternative value can be properly quoted, e.g. wget ${output:+ -o "$output"}
.
As always, this warning can be [[ignore]]d on a case-by-case basis.
this is especially relevant when BASH many not be available for the array work around. For example, use in eval or in command options where script has total control of the variables...
FLAGS="-av -e 'ssh -x' --delete --delete-excluded"
...
# shellcheck disable=SC2086
eval rsync $FLAGS ~/dir remote_host:dir
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Double quote to prevent globbing and word splitting. Open
BASE_URL=$(echo ${BASE_URL} | sed "s/\/\/$/\//g" )
- Read upRead up
- Exclude checks
Double quote to prevent globbing and word splitting.
Problematic code:
echo $1
for i in $*; do :; done # this done and the next one also applies to expanding arrays.
for i in $@; do :; done
Correct code:
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'
Rationale
The first code looks like "print the first argument". It's actually "Split the first argument by IFS (spaces, tabs and line feeds). Expand each of them as if it was a glob. Join all the resulting strings and filenames with spaces. Print the result."
The second one looks like "iterate through all arguments". It's actually "join all the arguments by the first character of IFS (space), split them by IFS and expand each of them as globs, and iterate on the resulting list". The third one skips the joining part.
Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.
Strictly speaking, only expansions themselves need to be quoted, but for stylistic reasons, entire arguments with multiple variable and literal parts are often quoted as one:
$HOME/$dir/dist/bin/$file # Unquoted (bad)
"$HOME"/"$dir"/dist/bin/"$file" # Minimal quoting (good)
"$HOME/$dir/dist/bin/$file" # Canonical quoting (good)
When quoting composite arguments, make sure to exclude globs and brace expansions, which lose their special meaning in double quotes: "$HOME/$dir/src/*.c"
will not expand, but "$HOME/$dir/src"/*.c
will.
Note that $( )
starts a new context, and variables in it have to be quoted independently:
echo "This $variable is quoted $(but this $variable is not)"
echo "This $variable is quoted $(and now this "$variable" is too)"
Exceptions
Sometimes you want to split on spaces, like when building a command line:
options="-j 5 -B"
make $options file
Just quoting this doesn't work. Instead, you should have used an array (bash, ksh, zsh):
options=(-j 5 -B) # ksh: set -A options -- -j 5 -B
make "${options[@]}" file
or a function (POSIX):
make_with_flags() { make -j 5 -B "$@"; }
make_with_flags file
To split on spaces but not perform glob expansion, Posix has a set -f
to disable globbing. You can disable word splitting by setting IFS=''
.
Similarly, you might want an optional argument:
debug=""
[[ $1 == "--trace-commands" ]] && debug="-x"
bash $debug script
Quoting this doesn't work, since in the default case, "$debug"
would expand to one empty argument while $debug
would expand into zero arguments. In this case, you can use an array with zero or one elements as outlined above, or you can use an unquoted expansion with an alternate value:
debug=""
[[ $1 == "--trace-commands" ]] && debug="yes"
bash ${debug:+"-x"} script
This is better than an unquoted value because the alternative value can be properly quoted, e.g. wget ${output:+ -o "$output"}
.
As always, this warning can be [[ignore]]d on a case-by-case basis.
this is especially relevant when BASH many not be available for the array work around. For example, use in eval or in command options where script has total control of the variables...
FLAGS="-av -e 'ssh -x' --delete --delete-excluded"
...
# shellcheck disable=SC2086
eval rsync $FLAGS ~/dir remote_host:dir
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Quote this to prevent word splitting. Open
if ! tar -tf $(getCodeDumpFilename) | grep '^index.php'
- Read upRead up
- Exclude checks
Quote this to prevent word splitting
Problematic code:
ls -l $(getfilename)
Correct code:
# getfilename outputs 1 file
ls -l "$(getfilename)"
# getfilename outputs multiple files, linefeed separated
getfilename | while IFS='' read -r line
do
ls -l "$line"
done
Rationale:
When command expansions are unquoted, word splitting and globbing will occur. This often manifests itself by breaking when filenames contain spaces.
Trying to fix it by adding quotes or escapes to the data will not work. Instead, quote the command substitution itself.
If the command substitution outputs multiple pieces of data, use a loop instead.
Exceptions
In rare cases you actually want word splitting, such as in
gcc $(pkg-config --libs openssl) client.c
This is because pkg-config
outputs -lssl -lcrypto
, which you want to break up by spaces into -lssl
and -lcrypto
. An alternative is to put the variables to an array and expand it:
args=( $(pkg-config --libs openssl) )
gcc "${args[@]}" client.c
The power of using an array becomes evident when you want to combine, for example, the command result with user-provided arguments:
compile () {
args=( $(pkg-config --libs openssl) "${@}" )
gcc "${args[@]}" client.c
}
compile -DDEBUG
+ gcc -lssl -lcrypto -DDEBUG client.c
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Argument mixes string and array. Use * or separate argument. Open
echo "The following directories are missing: ${missingDirectories[@]}";
- Read upRead up
- Exclude checks
Argument mixes string and array. Use * or separate argument.
Problematic code:
printf "Error: %s\n" "Bad parameters: $@"
Correct code:
printf "Error: %s\n" "Bad parameters: $*"
Rationale:
The behavior when concatenating a string and array is rarely intended. The preceeding string is prefixed to the first array element, while the succeeding string is appended to the last one. The middle array elements are unaffected.
E.g., with the parameters foo
,bar
,baz
, "--flag=$@"
is equivalent to 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, put the array expansion in its own argument.
Exceptions
None.
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Double quote to prevent globbing and word splitting. Open
$BIN_PHP $BIN_MAGE --quiet config:set 'catalog/search/engine' $(getRecommendedSearchEngineForVersion)
- Read upRead up
- Exclude checks
Double quote to prevent globbing and word splitting.
Problematic code:
echo $1
for i in $*; do :; done # this done and the next one also applies to expanding arrays.
for i in $@; do :; done
Correct code:
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'
Rationale
The first code looks like "print the first argument". It's actually "Split the first argument by IFS (spaces, tabs and line feeds). Expand each of them as if it was a glob. Join all the resulting strings and filenames with spaces. Print the result."
The second one looks like "iterate through all arguments". It's actually "join all the arguments by the first character of IFS (space), split them by IFS and expand each of them as globs, and iterate on the resulting list". The third one skips the joining part.
Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.
Strictly speaking, only expansions themselves need to be quoted, but for stylistic reasons, entire arguments with multiple variable and literal parts are often quoted as one:
$HOME/$dir/dist/bin/$file # Unquoted (bad)
"$HOME"/"$dir"/dist/bin/"$file" # Minimal quoting (good)
"$HOME/$dir/dist/bin/$file" # Canonical quoting (good)
When quoting composite arguments, make sure to exclude globs and brace expansions, which lose their special meaning in double quotes: "$HOME/$dir/src/*.c"
will not expand, but "$HOME/$dir/src"/*.c
will.
Note that $( )
starts a new context, and variables in it have to be quoted independently:
echo "This $variable is quoted $(but this $variable is not)"
echo "This $variable is quoted $(and now this "$variable" is too)"
Exceptions
Sometimes you want to split on spaces, like when building a command line:
options="-j 5 -B"
make $options file
Just quoting this doesn't work. Instead, you should have used an array (bash, ksh, zsh):
options=(-j 5 -B) # ksh: set -A options -- -j 5 -B
make "${options[@]}" file
or a function (POSIX):
make_with_flags() { make -j 5 -B "$@"; }
make_with_flags file
To split on spaces but not perform glob expansion, Posix has a set -f
to disable globbing. You can disable word splitting by setting IFS=''
.
Similarly, you might want an optional argument:
debug=""
[[ $1 == "--trace-commands" ]] && debug="-x"
bash $debug script
Quoting this doesn't work, since in the default case, "$debug"
would expand to one empty argument while $debug
would expand into zero arguments. In this case, you can use an array with zero or one elements as outlined above, or you can use an unquoted expansion with an alternate value:
debug=""
[[ $1 == "--trace-commands" ]] && debug="yes"
bash ${debug:+"-x"} script
This is better than an unquoted value because the alternative value can be properly quoted, e.g. wget ${output:+ -o "$output"}
.
As always, this warning can be [[ignore]]d on a case-by-case basis.
this is especially relevant when BASH many not be available for the array work around. For example, use in eval or in command options where script has total control of the variables...
FLAGS="-av -e 'ssh -x' --delete --delete-excluded"
...
# shellcheck disable=SC2086
eval rsync $FLAGS ~/dir remote_host:dir
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Double quote to prevent globbing and word splitting. Open
mysql -h $MYSQL_HOST -u $DB_USER --password=$MYSQL_PWD $DB_NAME -e "DELETE FROM store"
- Read upRead up
- Exclude checks
Double quote to prevent globbing and word splitting.
Problematic code:
echo $1
for i in $*; do :; done # this done and the next one also applies to expanding arrays.
for i in $@; do :; done
Correct code:
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'
Rationale
The first code looks like "print the first argument". It's actually "Split the first argument by IFS (spaces, tabs and line feeds). Expand each of them as if it was a glob. Join all the resulting strings and filenames with spaces. Print the result."
The second one looks like "iterate through all arguments". It's actually "join all the arguments by the first character of IFS (space), split them by IFS and expand each of them as globs, and iterate on the resulting list". The third one skips the joining part.
Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.
Strictly speaking, only expansions themselves need to be quoted, but for stylistic reasons, entire arguments with multiple variable and literal parts are often quoted as one:
$HOME/$dir/dist/bin/$file # Unquoted (bad)
"$HOME"/"$dir"/dist/bin/"$file" # Minimal quoting (good)
"$HOME/$dir/dist/bin/$file" # Canonical quoting (good)
When quoting composite arguments, make sure to exclude globs and brace expansions, which lose their special meaning in double quotes: "$HOME/$dir/src/*.c"
will not expand, but "$HOME/$dir/src"/*.c
will.
Note that $( )
starts a new context, and variables in it have to be quoted independently:
echo "This $variable is quoted $(but this $variable is not)"
echo "This $variable is quoted $(and now this "$variable" is too)"
Exceptions
Sometimes you want to split on spaces, like when building a command line:
options="-j 5 -B"
make $options file
Just quoting this doesn't work. Instead, you should have used an array (bash, ksh, zsh):
options=(-j 5 -B) # ksh: set -A options -- -j 5 -B
make "${options[@]}" file
or a function (POSIX):
make_with_flags() { make -j 5 -B "$@"; }
make_with_flags file
To split on spaces but not perform glob expansion, Posix has a set -f
to disable globbing. You can disable word splitting by setting IFS=''
.
Similarly, you might want an optional argument:
debug=""
[[ $1 == "--trace-commands" ]] && debug="-x"
bash $debug script
Quoting this doesn't work, since in the default case, "$debug"
would expand to one empty argument while $debug
would expand into zero arguments. In this case, you can use an array with zero or one elements as outlined above, or you can use an unquoted expansion with an alternate value:
debug=""
[[ $1 == "--trace-commands" ]] && debug="yes"
bash ${debug:+"-x"} script
This is better than an unquoted value because the alternative value can be properly quoted, e.g. wget ${output:+ -o "$output"}
.
As always, this warning can be [[ignore]]d on a case-by-case basis.
this is especially relevant when BASH many not be available for the array work around. For example, use in eval or in command options where script has total control of the variables...
FLAGS="-av -e 'ssh -x' --delete --delete-excluded"
...
# shellcheck disable=SC2086
eval rsync $FLAGS ~/dir remote_host:dir
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
ROOT appears unused. Verify it or export it. Open
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)";
- Read upRead up
- Exclude checks
foo appears unused. Verify it or export it.
Problematic code:
foo=42
echo "$FOO"
Correct code:
foo=42
echo "$foo"
Rationale:
Variables not used for anything are often associated with bugs, so ShellCheck warns about them.
Also note that something like local let foo=42
does not make a let
statement local -- it instead declares an additional local variable named let
.
Exceptions
ShellCheck may not always realize that the variable is in use (especially with indirection), and may not realize you don't care (with throwaway variables or unimplemented features).
For throwaway variables, consider using _
as a dummy:
read _ last _ zip _ _ <<< "$str"
echo "$last, $zip"
or use a directive to disable the warning:
# shellcheck disable=SC2034
read first last email zip lat lng <<< "$str"
echo "$last, $zip"
For indirection, there's not much you can do without rewriting to use arrays or similar:
bar=42 # will always appear unused
foo=bar
echo "${!foo}"
This is expected behavior, and not a bug. There is no good way to statically analyze indirection in shell scripts, just like static C analyzers have a hard time preventing segfaults.
As always, there are ways to [[ignore]] this and other messages if they frequently get in your way.
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Assigning an array to a string! Assign as array, or use * instead of @ to concatenate. Open
GLOBAL_ARGS="$@"
- Read upRead up
- Exclude checks
Assigning an array to a string! Assign as array, or use * instead of @ to concatenate.
Problematic code:
var=$@
for i in $var; do ..; done
or
set -- Hello World
msg=$@
echo "You said $msg"
Correct code:
var=( "$@" )
for i in "${var[@]}"; do ..; done
or
set -- Hello World
msg=$*
echo "You said $msg"
Rationale:
Arrays and $@
can contain multiple elements. Simple variables contain only one. When assigning multiple elements to one element, the default behavior depends on the shell (bash concatenates with spaces, zsh concatenates with first char of IFS
).
Since doing this usually indicates a bug, ShellCheck warns and asks you to be explicit about what you want.
If you want to assign N elements as N elements, use an array, e.g. myArray=( "$@" )
.
If you want to assign N elements as 1 element by concatenating them, use *
instead of @
, e.g. myVar=${myArray[*]}
(this separates elements with the first character of IFS
, usually space).
The same is true for ${@: -1}
, which results in 0 or 1 elements: var=${*: -1}
assigns the last element or an empty string.
Exceptions
None.
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Double quote to prevent globbing and word splitting. Open
CMD="tar $(getStripComponentsValue ${EXTRACT_FILENAME}) -xf ${EXTRACT_FILENAME} $1"
- Read upRead up
- Exclude checks
Double quote to prevent globbing and word splitting.
Problematic code:
echo $1
for i in $*; do :; done # this done and the next one also applies to expanding arrays.
for i in $@; do :; done
Correct code:
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'
Rationale
The first code looks like "print the first argument". It's actually "Split the first argument by IFS (spaces, tabs and line feeds). Expand each of them as if it was a glob. Join all the resulting strings and filenames with spaces. Print the result."
The second one looks like "iterate through all arguments". It's actually "join all the arguments by the first character of IFS (space), split them by IFS and expand each of them as globs, and iterate on the resulting list". The third one skips the joining part.
Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.
Strictly speaking, only expansions themselves need to be quoted, but for stylistic reasons, entire arguments with multiple variable and literal parts are often quoted as one:
$HOME/$dir/dist/bin/$file # Unquoted (bad)
"$HOME"/"$dir"/dist/bin/"$file" # Minimal quoting (good)
"$HOME/$dir/dist/bin/$file" # Canonical quoting (good)
When quoting composite arguments, make sure to exclude globs and brace expansions, which lose their special meaning in double quotes: "$HOME/$dir/src/*.c"
will not expand, but "$HOME/$dir/src"/*.c
will.
Note that $( )
starts a new context, and variables in it have to be quoted independently:
echo "This $variable is quoted $(but this $variable is not)"
echo "This $variable is quoted $(and now this "$variable" is too)"
Exceptions
Sometimes you want to split on spaces, like when building a command line:
options="-j 5 -B"
make $options file
Just quoting this doesn't work. Instead, you should have used an array (bash, ksh, zsh):
options=(-j 5 -B) # ksh: set -A options -- -j 5 -B
make "${options[@]}" file
or a function (POSIX):
make_with_flags() { make -j 5 -B "$@"; }
make_with_flags file
To split on spaces but not perform glob expansion, Posix has a set -f
to disable globbing. You can disable word splitting by setting IFS=''
.
Similarly, you might want an optional argument:
debug=""
[[ $1 == "--trace-commands" ]] && debug="-x"
bash $debug script
Quoting this doesn't work, since in the default case, "$debug"
would expand to one empty argument while $debug
would expand into zero arguments. In this case, you can use an array with zero or one elements as outlined above, or you can use an unquoted expansion with an alternate value:
debug=""
[[ $1 == "--trace-commands" ]] && debug="yes"
bash ${debug:+"-x"} script
This is better than an unquoted value because the alternative value can be properly quoted, e.g. wget ${output:+ -o "$output"}
.
As always, this warning can be [[ignore]]d on a case-by-case basis.
this is especially relevant when BASH many not be available for the array work around. For example, use in eval or in command options where script has total control of the variables...
FLAGS="-av -e 'ssh -x' --delete --delete-excluded"
...
# shellcheck disable=SC2086
eval rsync $FLAGS ~/dir remote_host:dir
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Declare and assign separately to avoid masking return values. Open
local codeDumpFileSize="$(wc -c ${codeDumpFilenamePath} | awk '{print $1}')"
- Read upRead up
- Exclude checks
Declare and assign separately to avoid masking return values.
Problematic code:
export foo="$(mycmd)"
Correct code:
foo=$(mycmd)
export foo
Rationale:
In the original code, the return value of mycmd
is ignored, and export
will instead always return true. This may prevent conditionals, set -e
and traps from working correctly.
When first marked for export and assigned separately, the return value of the assignment will be that of mycmd
. This avoids the problem.
Exceptions:
If you intend to ignore the return value of an assignment, you can either ignore this warning or use
foo=$(mycmd) || true
export foo
Shellcheck does not warn about export foo=bar
because bar
is a literal and not a command substitution with an independent return value. It also does not warn about local -r foo=$(cmd)
, where declaration and assignment must be in the same command.
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Expressions don't expand in single quotes, use double quotes for that. Open
addToBootstrap ' throw new \Exception("Remote Host ${REMOTE_HOST} is unavailable, check your network settings or VPN connection");'
- Read upRead up
- Exclude checks
Expressions don't expand in single quotes, use double quotes for that.
Problematic code:
name=World
echo 'Hello $name'
Correct code:
name=World
echo "Hello $name"
Rationale:
Single quotes prevent expansion of everything, including variables and command substitution.
If you want to use the values of variables and such, use double quotes instead.
Note that if you have other items that needs single quoting, you can use both in a single word:
echo '$1 USD is '"$rate GBP"
Exceptions
If you want $stuff
to be a literal dollar sign followed by the characters "stuff", you can [[ignore]] this message.
ShellCheck tries to be smart about it, and won't warn when this is used with awk, perl and similar, but there are some inherent ambiguities like 'I have $1 in my wallet'
, which could be "one dollar" or "whatever's in the first parameter".
In the particular case of sed
, ShellCheck uses additional heuristics to try to separate cases like 's/$foo/bar/'
(failing to replace the variable $foo
) with from the false positives like '$d'
(delete last line). If you're still triggering these, consider being more generous with your spaces: use $ { s/foo/bar; }
instead of ${s/foo/bar/;}
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @). Open
if [[ "${missingDirectories[@]-}" || "${missingFiles[@]-}" ]]
- Read upRead up
- Exclude checks
Arrays implicitly concatenate in [[ ]]
. Use a loop (or explicit * instead of @).
Problematic code:
ext=png
allowedExt=(jpg bmp png)
[[ "$ext" = "${allowedExt[@]}" ]] && echo "Extension is valid"
Correct code:
ext=png
allowedExt=(jpg bmp png)
for value in "${allowedExt[@]}"
do
[[ "$ext" = "$value" ]] && echo "Extension is valid"
done
Rationale:
Array expansions in [[ .. ]]
will implicitly concatenate into a single string, much like in assignments. The problematic code is equivalent to [ "$ext" = "jpg bmp png" ]
.
Instead, use a for
loop to iterate over values, and apply your condition to each.
Alternatively, if you do want to concatenate all the values in the array into a single string for your test, use "$*"
or "${array[*]}"
to make this explicit.
Exceptions:
None.
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @). Open
if [[ "${missingDirectories[@]-}" || "${missingFiles[@]-}" ]]
- Read upRead up
- Exclude checks
Arrays implicitly concatenate in [[ ]]
. Use a loop (or explicit * instead of @).
Problematic code:
ext=png
allowedExt=(jpg bmp png)
[[ "$ext" = "${allowedExt[@]}" ]] && echo "Extension is valid"
Correct code:
ext=png
allowedExt=(jpg bmp png)
for value in "${allowedExt[@]}"
do
[[ "$ext" = "$value" ]] && echo "Extension is valid"
done
Rationale:
Array expansions in [[ .. ]]
will implicitly concatenate into a single string, much like in assignments. The problematic code is equivalent to [ "$ext" = "jpg bmp png" ]
.
Instead, use a for
loop to iterate over values, and apply your condition to each.
Alternatively, if you do want to concatenate all the values in the array into a single string for your test, use "$*"
or "${array[*]}"
to make this explicit.
Exceptions:
None.
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Double quote to prevent globbing and word splitting. Open
$BIN_PHP $BIN_MAGE module:status | grep -E 'Magento_Elasticsearch*|Magento_AdvancedSearch|Magento_InventoryElasticsearch' | grep -v List | grep -v None | grep -v -e '^$' | xargs $BIN_PHP $BIN_MAGE module:disable
- Read upRead up
- Exclude checks
Double quote to prevent globbing and word splitting.
Problematic code:
echo $1
for i in $*; do :; done # this done and the next one also applies to expanding arrays.
for i in $@; do :; done
Correct code:
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'
Rationale
The first code looks like "print the first argument". It's actually "Split the first argument by IFS (spaces, tabs and line feeds). Expand each of them as if it was a glob. Join all the resulting strings and filenames with spaces. Print the result."
The second one looks like "iterate through all arguments". It's actually "join all the arguments by the first character of IFS (space), split them by IFS and expand each of them as globs, and iterate on the resulting list". The third one skips the joining part.
Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.
Strictly speaking, only expansions themselves need to be quoted, but for stylistic reasons, entire arguments with multiple variable and literal parts are often quoted as one:
$HOME/$dir/dist/bin/$file # Unquoted (bad)
"$HOME"/"$dir"/dist/bin/"$file" # Minimal quoting (good)
"$HOME/$dir/dist/bin/$file" # Canonical quoting (good)
When quoting composite arguments, make sure to exclude globs and brace expansions, which lose their special meaning in double quotes: "$HOME/$dir/src/*.c"
will not expand, but "$HOME/$dir/src"/*.c
will.
Note that $( )
starts a new context, and variables in it have to be quoted independently:
echo "This $variable is quoted $(but this $variable is not)"
echo "This $variable is quoted $(and now this "$variable" is too)"
Exceptions
Sometimes you want to split on spaces, like when building a command line:
options="-j 5 -B"
make $options file
Just quoting this doesn't work. Instead, you should have used an array (bash, ksh, zsh):
options=(-j 5 -B) # ksh: set -A options -- -j 5 -B
make "${options[@]}" file
or a function (POSIX):
make_with_flags() { make -j 5 -B "$@"; }
make_with_flags file
To split on spaces but not perform glob expansion, Posix has a set -f
to disable globbing. You can disable word splitting by setting IFS=''
.
Similarly, you might want an optional argument:
debug=""
[[ $1 == "--trace-commands" ]] && debug="-x"
bash $debug script
Quoting this doesn't work, since in the default case, "$debug"
would expand to one empty argument while $debug
would expand into zero arguments. In this case, you can use an array with zero or one elements as outlined above, or you can use an unquoted expansion with an alternate value:
debug=""
[[ $1 == "--trace-commands" ]] && debug="yes"
bash ${debug:+"-x"} script
This is better than an unquoted value because the alternative value can be properly quoted, e.g. wget ${output:+ -o "$output"}
.
As always, this warning can be [[ignore]]d on a case-by-case basis.
this is especially relevant when BASH many not be available for the array work around. For example, use in eval or in command options where script has total control of the variables...
FLAGS="-av -e 'ssh -x' --delete --delete-excluded"
...
# shellcheck disable=SC2086
eval rsync $FLAGS ~/dir remote_host:dir
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Quote this to prevent word splitting. Open
echo $(grep 'table_prefix' app/etc/env.php | head -n1 | sed "s/[a-z'_ ]*[=][>][ ]*[']//" | sed "s/['][,]*//")
- Read upRead up
- Exclude checks
Quote this to prevent word splitting
Problematic code:
ls -l $(getfilename)
Correct code:
# getfilename outputs 1 file
ls -l "$(getfilename)"
# getfilename outputs multiple files, linefeed separated
getfilename | while IFS='' read -r line
do
ls -l "$line"
done
Rationale:
When command expansions are unquoted, word splitting and globbing will occur. This often manifests itself by breaking when filenames contain spaces.
Trying to fix it by adding quotes or escapes to the data will not work. Instead, quote the command substitution itself.
If the command substitution outputs multiple pieces of data, use a loop instead.
Exceptions
In rare cases you actually want word splitting, such as in
gcc $(pkg-config --libs openssl) client.c
This is because pkg-config
outputs -lssl -lcrypto
, which you want to break up by spaces into -lssl
and -lcrypto
. An alternative is to put the variables to an array and expand it:
args=( $(pkg-config --libs openssl) )
gcc "${args[@]}" client.c
The power of using an array becomes evident when you want to combine, for example, the command result with user-provided arguments:
compile () {
args=( $(pkg-config --libs openssl) "${@}" )
gcc "${args[@]}" client.c
}
compile -DDEBUG
+ gcc -lssl -lcrypto -DDEBUG client.c
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Double quote to prevent globbing and word splitting. Open
$BIN_PHP $BIN_MAGE module:status | grep -E 'Magento_Elasticsearch*|Magento_AdvancedSearch|Magento_InventoryElasticsearch' | grep -v List | grep -v None | grep -v -e '^$' | xargs $BIN_PHP $BIN_MAGE module:disable
- Read upRead up
- Exclude checks
Double quote to prevent globbing and word splitting.
Problematic code:
echo $1
for i in $*; do :; done # this done and the next one also applies to expanding arrays.
for i in $@; do :; done
Correct code:
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'
Rationale
The first code looks like "print the first argument". It's actually "Split the first argument by IFS (spaces, tabs and line feeds). Expand each of them as if it was a glob. Join all the resulting strings and filenames with spaces. Print the result."
The second one looks like "iterate through all arguments". It's actually "join all the arguments by the first character of IFS (space), split them by IFS and expand each of them as globs, and iterate on the resulting list". The third one skips the joining part.
Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.
Strictly speaking, only expansions themselves need to be quoted, but for stylistic reasons, entire arguments with multiple variable and literal parts are often quoted as one:
$HOME/$dir/dist/bin/$file # Unquoted (bad)
"$HOME"/"$dir"/dist/bin/"$file" # Minimal quoting (good)
"$HOME/$dir/dist/bin/$file" # Canonical quoting (good)
When quoting composite arguments, make sure to exclude globs and brace expansions, which lose their special meaning in double quotes: "$HOME/$dir/src/*.c"
will not expand, but "$HOME/$dir/src"/*.c
will.
Note that $( )
starts a new context, and variables in it have to be quoted independently:
echo "This $variable is quoted $(but this $variable is not)"
echo "This $variable is quoted $(and now this "$variable" is too)"
Exceptions
Sometimes you want to split on spaces, like when building a command line:
options="-j 5 -B"
make $options file
Just quoting this doesn't work. Instead, you should have used an array (bash, ksh, zsh):
options=(-j 5 -B) # ksh: set -A options -- -j 5 -B
make "${options[@]}" file
or a function (POSIX):
make_with_flags() { make -j 5 -B "$@"; }
make_with_flags file
To split on spaces but not perform glob expansion, Posix has a set -f
to disable globbing. You can disable word splitting by setting IFS=''
.
Similarly, you might want an optional argument:
debug=""
[[ $1 == "--trace-commands" ]] && debug="-x"
bash $debug script
Quoting this doesn't work, since in the default case, "$debug"
would expand to one empty argument while $debug
would expand into zero arguments. In this case, you can use an array with zero or one elements as outlined above, or you can use an unquoted expansion with an alternate value:
debug=""
[[ $1 == "--trace-commands" ]] && debug="yes"
bash ${debug:+"-x"} script
This is better than an unquoted value because the alternative value can be properly quoted, e.g. wget ${output:+ -o "$output"}
.
As always, this warning can be [[ignore]]d on a case-by-case basis.
this is especially relevant when BASH many not be available for the array work around. For example, use in eval or in command options where script has total control of the variables...
FLAGS="-av -e 'ssh -x' --delete --delete-excluded"
...
# shellcheck disable=SC2086
eval rsync $FLAGS ~/dir remote_host:dir
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Double quote to prevent globbing and word splitting. Open
$BIN_PHP $BIN_MAGE module:status | grep Magento_LiveSearch | grep -v List | grep -v None | grep -v -e '^$' | xargs $BIN_PHP $BIN_MAGE module:enable
- Read upRead up
- Exclude checks
Double quote to prevent globbing and word splitting.
Problematic code:
echo $1
for i in $*; do :; done # this done and the next one also applies to expanding arrays.
for i in $@; do :; done
Correct code:
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'
Rationale
The first code looks like "print the first argument". It's actually "Split the first argument by IFS (spaces, tabs and line feeds). Expand each of them as if it was a glob. Join all the resulting strings and filenames with spaces. Print the result."
The second one looks like "iterate through all arguments". It's actually "join all the arguments by the first character of IFS (space), split them by IFS and expand each of them as globs, and iterate on the resulting list". The third one skips the joining part.
Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.
Strictly speaking, only expansions themselves need to be quoted, but for stylistic reasons, entire arguments with multiple variable and literal parts are often quoted as one:
$HOME/$dir/dist/bin/$file # Unquoted (bad)
"$HOME"/"$dir"/dist/bin/"$file" # Minimal quoting (good)
"$HOME/$dir/dist/bin/$file" # Canonical quoting (good)
When quoting composite arguments, make sure to exclude globs and brace expansions, which lose their special meaning in double quotes: "$HOME/$dir/src/*.c"
will not expand, but "$HOME/$dir/src"/*.c
will.
Note that $( )
starts a new context, and variables in it have to be quoted independently:
echo "This $variable is quoted $(but this $variable is not)"
echo "This $variable is quoted $(and now this "$variable" is too)"
Exceptions
Sometimes you want to split on spaces, like when building a command line:
options="-j 5 -B"
make $options file
Just quoting this doesn't work. Instead, you should have used an array (bash, ksh, zsh):
options=(-j 5 -B) # ksh: set -A options -- -j 5 -B
make "${options[@]}" file
or a function (POSIX):
make_with_flags() { make -j 5 -B "$@"; }
make_with_flags file
To split on spaces but not perform glob expansion, Posix has a set -f
to disable globbing. You can disable word splitting by setting IFS=''
.
Similarly, you might want an optional argument:
debug=""
[[ $1 == "--trace-commands" ]] && debug="-x"
bash $debug script
Quoting this doesn't work, since in the default case, "$debug"
would expand to one empty argument while $debug
would expand into zero arguments. In this case, you can use an array with zero or one elements as outlined above, or you can use an unquoted expansion with an alternate value:
debug=""
[[ $1 == "--trace-commands" ]] && debug="yes"
bash ${debug:+"-x"} script
This is better than an unquoted value because the alternative value can be properly quoted, e.g. wget ${output:+ -o "$output"}
.
As always, this warning can be [[ignore]]d on a case-by-case basis.
this is especially relevant when BASH many not be available for the array work around. For example, use in eval or in command options where script has total control of the variables...
FLAGS="-av -e 'ssh -x' --delete --delete-excluded"
...
# shellcheck disable=SC2086
eval rsync $FLAGS ~/dir remote_host:dir
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.
Consider using { cmd1; cmd2; } >> file instead of individual redirects. Open
echo "HTTP_HOST=http://$CURRENT_DIR_NAME.127.0.0.1.nip.io/" >> ../.m2install.conf
- Read upRead up
- Exclude checks
Consider using { cmd1; cmd2; } >> file instead of individual redirects.
Problematic code:
echo foo >> file
date >> file
cat stuff >> file
Correct code:
{
echo foo
date
cat stuff
} >> file
Rationale:
Rather than adding >> something
after every single line, you can simply group the relevant commands and redirect the group.
Exceptions
This is mainly a stylistic issue, and can freely be ignored.
Notice
Original content from the ShellCheck https://github.com/koalaman/shellcheck/wiki.