Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
{ is only recognized as the start of a command group when it's a separate token.
If it's not a separate token, like in the problematic example, it will be considered a literal character, as if writing "{echo" with quotes, and therefore usually cause a syntax error.
foo references arguments, but none are ever passed.
Problematic code:
sayhello(){
echo"Hello $1"
}
sayhello
./myscript World just prints "Hello " instead of "Hello World".
Correct code:
sayhello(){
echo"Hello $1"
}
sayhello "$@"
./myscript World now prints "Hello World".
Rationale:
In a function, $1 and up refers to the function's parameters, not the script's parameters.
If you want to process your script's parameters in a function, you have to explicitly pass them. You can do this with myfunction "$@".
Note that "$@" refers to the current context's positional parameters, so if you call a function from a function, you have to pass in "$@" to both of them:
first(){ second "$@";}
second(){echo"The first script parameter is: $1";}
first "$@"
Exceptions
If the parameters are optional and you currently just don't want to use them, you can ignore this message.
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
This {/} is literal. Check expression (missing ;/\n?) or quote it.
Problematic code:
rmf(){rm-f"$@"}
or
evalecho\${foo}
Correct code:
rmf(){rm-f"$@";}
and
eval"echo \${foo}"
Rationale:
Curly brackets are normally used as syntax in parameter expansion, command grouping and brace expansion.
However, if they don't appear alone at the start of an expression or as part of a parameter or brace expansion, the shell silently treats them as literals. This frequently indicates a bug, so ShellCheck warns about it.
In the example function, the } is literal because it's not at the start of an expression. We fix it by adding a ; before it.
In the example eval, the code works fine. However, we can quiet the warning and follow good practice by adding quotes around the literal data.
ShellCheck does not warn about {}, since this is frequently used with find and rarely indicates a bug.
Exceptions
This error is harmless when the curly brackets are supposed to be literal, in e.g. awk {'print $1'}. However, it's cleaner and less error prone to simply include them inside the quotes: awk '{print $1}'.
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
Assigning an array to a string! Assign as array, or use * instead of @ to concatenate.
Problematic code:
var=$@
foriin$var;do..;done
or
set -- Hello World
msg=$@
echo"You said $msg"
Correct code:
var=("$@")
foriin"${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.
This {/} is literal. Check expression (missing ;/\n?) or quote it.
Problematic code:
rmf(){rm-f"$@"}
or
evalecho\${foo}
Correct code:
rmf(){rm-f"$@";}
and
eval"echo \${foo}"
Rationale:
Curly brackets are normally used as syntax in parameter expansion, command grouping and brace expansion.
However, if they don't appear alone at the start of an expression or as part of a parameter or brace expansion, the shell silently treats them as literals. This frequently indicates a bug, so ShellCheck warns about it.
In the example function, the } is literal because it's not at the start of an expression. We fix it by adding a ; before it.
In the example eval, the code works fine. However, we can quiet the warning and follow good practice by adding quotes around the literal data.
ShellCheck does not warn about {}, since this is frequently used with find and rarely indicates a bug.
Exceptions
This error is harmless when the curly brackets are supposed to be literal, in e.g. awk {'print $1'}. However, it's cleaner and less error prone to simply include them inside the quotes: awk '{print $1}'.
This {/} is literal. Check expression (missing ;/\n?) or quote it.
Problematic code:
rmf(){rm-f"$@"}
or
evalecho\${foo}
Correct code:
rmf(){rm-f"$@";}
and
eval"echo \${foo}"
Rationale:
Curly brackets are normally used as syntax in parameter expansion, command grouping and brace expansion.
However, if they don't appear alone at the start of an expression or as part of a parameter or brace expansion, the shell silently treats them as literals. This frequently indicates a bug, so ShellCheck warns about it.
In the example function, the } is literal because it's not at the start of an expression. We fix it by adding a ; before it.
In the example eval, the code works fine. However, we can quiet the warning and follow good practice by adding quotes around the literal data.
ShellCheck does not warn about {}, since this is frequently used with find and rarely indicates a bug.
Exceptions
This error is harmless when the curly brackets are supposed to be literal, in e.g. awk {'print $1'}. However, it's cleaner and less error prone to simply include them inside the quotes: awk '{print $1}'.
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...
Double quote to prevent globbing and word splitting.
Problematic code:
echo$1
foriin$*;do:;done# this done and the next one also applies to expanding arrays.
foriin$@;do:;done
Correct code:
echo"$1"
foriin"$@";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:
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$optionsfile
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-j5-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...