Tips depend on target shell and yours is unknown. Add a shebang.
Problematic code:
echo"$RANDOM"# Does this work?
Correct code:
#!/bin/sh
echo"$RANDOM"# Unsupported in sh. Produces warning.
or
#!/bin/bash
echo"$RANDOM"# Supported in bash. No warnings.
Rationale:
Different shells support different features. To give effective advice, ShellCheck needs to know which shell your script is going to run on. You will get a different numbers of warnings about different things depending on your target shell.
ShellCheck normally determines your target shell from the shebang (having e.g. #!/bin/sh as the first line). The shell can also be specified from the CLI with -s, e.g. shellcheck -s sh file.
If you don't specify shebang nor -s, ShellCheck gives this message and proceeds with some default (bash).
Note that this error can not be ignored with a [[directive]]. It is not a suggestion to improve your script, but a warning that ShellCheck lacks information it needs to be helpful.
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...
Can't follow non-constant source. Use a directive to specify location.
Problematic code:
."$(find_install_dir)/lib.sh"
Correct code:
# shellcheck source=src/lib.sh
."$(find_install_dir)/lib.sh"
Rationale:
ShellCheck is not able to include sourced files from paths that are determined at runtime. The file will not be read, potentially resulting in warnings about unassigned variables and similar.
Use a [[Directive]] to point shellcheck to a fixed location it can read instead.
Exceptions:
If you don't care that ShellCheck is unable to account for the file, specify # shellcheck source=/dev/null.
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.
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}'.
{ 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.
{ 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.
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}'.
{ 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.
{ 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.
Note: There is a [known bug](../issues/1036) in the current version when [directives](../wiki/Directive) appear within then clauses of if blocks that causes Shellcheck to report SC1072 on otherwise valid code. Avoid using directives within then clauses - instead place them at the top of the if block or another enclosing block. This is fixed on the online version and the next release.
Note: There is a [known bug](../issues/1036) in the current version when [directives](../wiki/Directive) appear within then clauses of if blocks that causes Shellcheck to report SC1072 on otherwise valid code. Avoid using directives within then clauses - instead place them at the top of the if block or another enclosing block. This is fixed on the online version and the next release.
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}'.
Note: There is a [known bug](../issues/1036) in the current version when [directives](../wiki/Directive) appear within then clauses of if blocks that causes Shellcheck to report SC1072 on otherwise valid code. Avoid using directives within then clauses - instead place them at the top of the if block or another enclosing block. This is fixed on the online version and the next release.