Use triple backticks rather than indenting for code.

Vidar Holen
2015-10-03 21:33:27 -07:00
parent c91b073c53
commit e073e65e5e
153 changed files with 1741 additions and 1049 deletions

@@ -8,7 +8,7 @@ yum groupinstall "Development Tools" -y
yum install gmp-devel # may need epel
sudo ln -s /usr/lib64/libgmp.so.3 /usr/lib64/libgmp.so.10
# From https://www.haskell.org/platform/linux.html#linux-generic
# From https://www.haskell.org/platform/linux.html#linux-generic
wget https://haskell.org/platform/download/7.10.2/haskell-platform-7.10.2-a-unknown-linux-deb7.tar.gz
tar xvzf haskell-platform-7.10.2-a-unknown-linux-deb7.tar.gz
./install-haskell-platform.sh

@@ -17,4 +17,4 @@ and `source` to tell ShellCheck where to find a sourced file:
Directives are scoped to the structure that follows it. For example, before a function it silences all warnings (or overrides all `source` statements) in the function Before a case statement, it silences all warnings in all branches of the case statement.
Silencing parser errors is purely cosmetic, and will not make ShellCheck continue.
Silencing parser errors is purely cosmetic, and will not make ShellCheck continue.

@@ -1,3 +1,3 @@
Welcome to the ShellCheck wiki! Any help with creating pages explaining errors is greatly appreciated!
Welcome to the ShellCheck wiki! Any help with creating pages explaining errors is greatly appreciated!
Just copy the [[Template]] and give it a name like [[SC1000]], and bask in unending appreciation from shell scripters world wide!

@@ -1,6 +1,6 @@
## Getting JUnit XML from ShellCheck
ShellCheck does not have a JUnit XML formatter, but here you can find `checkstyle2junit.xslt`, a XSLT program that converts from Checkstyle XML output to JUnit XML.
ShellCheck does not have a JUnit XML formatter, but here you can find `checkstyle2junit.xslt`, a XSLT program that converts from Checkstyle XML output to JUnit XML.
Here's shellcheck's checkstyle XML output:

@@ -8,12 +8,12 @@ Consider this script, with a missing double quote on line 1:
echo "Finished"
Bash says:
file: line 2: unexpected EOF while looking for matching `"'
file: line 3: syntax error: unexpected end of file
Shellcheck says:
In file line 1:
ssh host "$cmd
^-- SC1009: The mentioned parser error was in this simple command.
@@ -27,6 +27,6 @@ Shellcheck says:
1. One error showing the direct problem (SC1072, unexpected eof)
1. One error showing the construct being parsed (SC1073)
1. One info showing the outer construct being parsed (SC1009)
1. Potentially some specific suggestions, such as when missing a `fi`.
1. Potentially some specific suggestions, such as when missing a `fi`.
Here, shellcheck says that the command on line 1 is faulty, which makes it easier to find and fix the actual problem.
Here, shellcheck says that the command on line 1 is faulty, which makes it easier to find and fix the actual problem.

@@ -2,16 +2,20 @@
### Problematic code:
echo "$"
```sh
echo "$"
```
### Correct code:
echo "\$"
```sh
echo "\$"
```
### Rationale:
`$` is special in double quotes, but there are some cases where it's interpreted literally:
1. Following a backslash: `echo "\$"`
2. In a context where the shell can't make sense of it, such as at the end of the string, (`"foo$"`) or before some constructs (`"$'foo'"`).
2. In a context where the shell can't make sense of it, such as at the end of the string, (`"foo$"`) or before some constructs (`"$'foo'"`).
To avoid relying on strange and shell specific behavior, any `$` intended to be literal should be escaped with a backslash.

@@ -2,19 +2,27 @@
### Problematic code:
echo Yay \o/
```sh
echo Yay \o/
```
or
\git status
```sh
\git status
```
### Correct code:
echo 'Yay \o/'
```sh
echo 'Yay \o/'
```
or
command git status
```sh
command git status
```
### Rationale:
@@ -22,8 +30,8 @@ Escaping something that doesn't need escaping sometimes indicates a bug.
If the backslash was supposed to be literal, single quote it.
If the purpose is to run an external command rather than an alias, prefer `command`.
If the purpose is to run an external command rather than an alias, prefer `command`.
### Exceptions
If you have an alias and a function (as opposed to an external command), you can either ignore this message or use `"name"` instead of `\name` to quiet ShellCheck.
If you have an alias and a function (as opposed to an external command), you can either ignore this message or use `"name"` instead of `\name` to quiet ShellCheck.

@@ -2,19 +2,27 @@
### Problematic code:
# I want programs to show text in dutch!
LANGUAGE= nl
```sh
# I want programs to show text in dutch!
LANGUAGE= nl
```
# I want to run the nl command with English error messages!
LANGUAGE= nl
```sh
# I want to run the nl command with English error messages!
LANGUAGE= nl
```
### Correct code:
# I want programs to show text in dutch!
LANGUAGE=nl
```sh
# I want programs to show text in dutch!
LANGUAGE=nl
```
# I want to run the nl command with English error messages!
LANGUAGE='' nl
```sh
# I want to run the nl command with English error messages!
LANGUAGE='' nl
```
### Rationale:
@@ -22,7 +30,7 @@ It's easy to think that `LANGUAGE= nl` would assign `"nl"` to the variable `LANG
Instead, it runs `nl` (the "number lines" command) and sets `LANGUAGE` to an empty string in its environment.
Since trying to assign values this way is a common mistake, ShellCheck warns about it and asks you to be explicit when assigning empty strings (except for `IFS`, due to the common `IFS= read ..` idiom).
Since trying to assign values this way is a common mistake, ShellCheck warns about it and asks you to be explicit when assigning empty strings (except for `IFS`, due to the common `IFS= read ..` idiom).
### Exceptions
If you're familiar with this behavior and feel that the explicit version is unnecessary, you can [[ignore]] it.
If you're familiar with this behavior and feel that the explicit version is unnecessary, you can [[ignore]] it.

@@ -1,3 +1,3 @@
# The mentioned parser error was in ...
This info warning points to the start of what ShellCheck was parsing when it failed. See [Parser Error](Parser Error) for example and information.
This info warning points to the start of what ShellCheck was parsing when it failed. See [Parser Error](Parser Error) for example and information.

@@ -2,11 +2,11 @@
### Problematic code:
for f in *; do echo "$f" done
for f in *; do echo "$f" done
### Correct code:
for f in *; do echo "$f"; done
for f in *; do echo "$f"; done
### Rationale:
@@ -14,4 +14,4 @@
### Exceptions
If you're intentionally using `done` as a literal, you can quote it to make this clear to shellcheck (and also human readers), e.g. instead of `echo Task is done`, use `echo "Task is done"`. This makes no difference to the shell, but it will silence this warning.
If you're intentionally using `done` as a literal, you can quote it to make this clear to shellcheck (and also human readers), e.g. instead of `echo Task is done`, use `echo "Task is done"`. This makes no difference to the shell, but it will silence this warning.

@@ -2,18 +2,22 @@
### Problematic code:
echo “hello world”
```sh
echo “hello world”
```
### Correct code:
echo "hello world"
```sh
echo "hello world"
```
### Rationale:
Blog software and word processors frequently replaces ASCII quotes `""` with fancy Unicode quotes, `“”`. To bash, Unicode quotes are considered regular literals and not quotes at all.
Blog software and word processors frequently replaces ASCII quotes `""` with fancy Unicode quotes, `“”`. To bash, Unicode quotes are considered regular literals and not quotes at all.
Simply delete them and retype them in your editor.
Simply delete them and retype them in your editor.
### Exceptions
If you really want literal Unicode double quotes, you can put them in single quotes (or unicode single quotes in double quotes) to make shellcheck ignore them.
If you really want literal Unicode double quotes, you can put them in single quotes (or unicode single quotes in double quotes) to make shellcheck ignore them.

@@ -2,11 +2,15 @@
### Problematic code:
echo hello world
```sh
echo hello world
```
### Correct code:
echo 'hello world'
```sh
echo 'hello world'
```
### Rationale:
@@ -14,4 +18,4 @@ Some software, like OS X, Word and Wordpress, may automatically replace your reg
### Exceptions
None
None

@@ -4,4 +4,4 @@ You copy-pasted some code, probably from a blog or web site, which for formattin
To humans, a zero-width space is invisible and a non-breaking space is indistinguishable from a regular space, but the shell does not agree.
If you have just a few, delete the indiciated space/word and retype it. If you have tons, do a search&replace in your editor (copy-paste an offending space into the search field, and type a regular space into the replace field), or use `sed -e $'s/\xC2\xA0/ /g' -e $'s/\xE2\x80\x8b//g' -i yourfile` to remove them.
If you have just a few, delete the indiciated space/word and retype it. If you have tons, do a search&replace in your editor (copy-paste an offending space into the search field, and type a regular space into the replace field), or use `sed -e $'s/\xC2\xA0/ /g' -e $'s/\xE2\x80\x8b//g' -i yourfile` to remove them.

@@ -17,4 +17,4 @@ Bourne shells are very whitespace sensitive. Adding or removing spaces can drast
### Exceptions
None.
None.

@@ -2,13 +2,17 @@
### Problematic code:
echo "Ninth parameter: $9"
echo "Tenth parameter: $10"
```sh
echo "Ninth parameter: $9"
echo "Tenth parameter: $10"
```
### Correct code:
echo "Ninth parameter: $9"
echo "Tenth parameter: ${10}"
```sh
echo "Ninth parameter: $9"
echo "Tenth parameter: ${10}"
```
### Rationale:
@@ -18,4 +22,4 @@ Curly braces are needed to tell the shell that both digits are part of the param
### Exceptions
If you wanted the trailing digits to be literal, `${1}0` will make this clear to both humans and shellcheck.
If you wanted the trailing digits to be literal, `${1}0` will make this clear to both humans and shellcheck.

@@ -2,17 +2,21 @@
### Problematic code:
while IFS= read -r line
do
printf "%q\n" "$line"
done <<(curl -s http://example.com)
```sh
while IFS= read -r line
do
printf "%q\n" "$line"
done <<(curl -s http://example.com)
```
### Correct code:
while IFS= read -r line
do
printf "%q\n" "$line"
done < <(curl -s http://example.com)
```sh
while IFS= read -r line
do
printf "%q\n" "$line"
done < <(curl -s http://example.com)
```
### Rationale:
@@ -22,4 +26,4 @@ You probably meant to redirect `<` from process substitution `<(..)` instead. To
### Exceptions:
None.
None.

@@ -4,23 +4,29 @@
Any code using `<<-` that is indented with spaces. `cat -T script` shows
cat <<- foo
Hello world
foo
```sh
cat <<- foo
Hello world
foo
```
### Correct code:
Code using `<<-` must be indented with tabs. `cat -T script` shows
^Icat <<- foo
^I^IHello world
^Ifoo
```sh
^Icat <<- foo
^I^IHello world
^Ifoo
```
Or simply don't indent the end token:
cat <<- foo
Hello World
foo
```sh
cat <<- foo
Hello World
foo
```
### Rationale:
@@ -30,4 +36,4 @@ Your editor may be automatically replacing tabs with spaces, either when you typ
### Exceptions
None. But note that copy-pasting code to [shellcheck.net](http://www.shellcheck.net) may also turn correct tabs into spaces on some OS.
None. But note that copy-pasting code to [shellcheck.net](http://www.shellcheck.net) may also turn correct tabs into spaces on some OS.

@@ -2,11 +2,15 @@
### Problematic code:
foo &; bar
```sh
foo &; bar
```
### Correct code:
foo & bar
```sh
foo & bar
```
### Rationale:
@@ -16,4 +20,4 @@ Both `&` and `;` terminate the command. You should only use one of them.
### Exceptions
None.
None.

@@ -2,17 +2,21 @@
### Problematic code:
foo(input) {
echo "$input"
}
foo("hello world");
```sh
foo(input) {
echo "$input"
}
foo("hello world");
```
### Correct code:
foo() {
echo "$1"
}
foo "hello world"
```sh
foo() {
echo "$1"
}
foo "hello world"
```
### Rationale:
@@ -23,4 +27,4 @@ Shell script functions behave just like scripts and other commands:
### Exceptions:
None.
None.

@@ -2,22 +2,28 @@
### Problematic code:
$greeting="Hello World"
```sh
$greeting="Hello World"
```
### Correct code:
greeting="Hello World"
```sh
greeting="Hello World"
```
Alternatively, if the goal was to assign to a variable whose name is in another variable (indirection), use `declare`:
name=foo
declare "$name=hello world"
echo "$foo"
```sh
name=foo
declare "$name=hello world"
echo "$foo"
```
### Rationale:
Unlike Perl or PHP, `$` is not used when assigning to a variable.
Unlike Perl or PHP, `$` is not used when assigning to a variable.
### Exceptions
None
None

@@ -2,11 +2,15 @@
### Problematic code:
foo = 42
```sh
foo = 42
```
### Correct code:
foo=42
```sh
foo=42
```
### Rationale:
@@ -14,4 +18,4 @@ Shells are space sensitive. `foo=42` means to assign `42` to the variable `foo`.
### Exceptions
If you actually wanted to run a command named foo and provide `=` as the first argument, simply quote it to make ShellCheck be quiet: `foo "=" 42`.
If you actually wanted to run a command named foo and provide `=` as the first argument, simply quote it to make ShellCheck be quiet: `foo "=" 42`.

@@ -1,3 +1,3 @@
# Unexpected ..
See [Parser Error](Parser Error).
See [Parser Error](Parser Error).

@@ -3,27 +3,35 @@
### Problematic code:
echo "Your username is ´whoami´"
```sh
echo "Your username is ´whoami´"
```
### Correct code:
echo "Your username is $(whoami)" # Preferred
echo "Your username is `whoami`" # Deprecated, will give [SC2006]
```sh
echo "Your username is $(whoami)" # Preferred
echo "Your username is `whoami`" # Deprecated, will give [SC2006]
```
### Rationale:
In some fonts it's hard to tell ticks apart, but Bash strongly distinguishes between backticks (grave accent `` ` ``), forward ticks (acute accent `´`) and regular ticks (apostrophe `'`).
In some fonts it's hard to tell ticks apart, but Bash strongly distinguishes between backticks (grave accent `` ` ``), forward ticks (acute accent `´`) and regular ticks (apostrophe `'`).
Backticks start command expansions, while forward ticks are literal. To help spot bugs, ShellCheck parses backticks and forward ticks interchangeably.
Backticks start command expansions, while forward ticks are literal. To help spot bugs, ShellCheck parses backticks and forward ticks interchangeably.
### Exceptions
If you want to write out literal forward ticks, such as fancyful ascii quotation marks:
echo "``Proprietary software is an injustice.´´ - Richard Stallman"
```sh
echo "``Proprietary software is an injustice.´´ - Richard Stallman"
```
use single quotes instead:
echo '``Proprietary software is an injustice.´´ - Richard Stallman'
```sh
echo '``Proprietary software is an injustice.´´ - Richard Stallman'
```
To nest forward ticks in command expansion, use `$(..)` instead of `` `..` ``.
To nest forward ticks in command expansion, use `$(..)` instead of `` `..` ``.

@@ -2,17 +2,21 @@
### Problematic code:
greeting="hello
target="world"
```sh
greeting="hello
target="world"
```
### Correct code:
greeting="hello"
target="world"
```sh
greeting="hello"
target="world"
```
### Rationale:
The first line is missing a quote.
The first line is missing a quote.
ShellCheck warns when it detects multi-line double quoted, single quoted or backticked strings when the character that follows it looks out of place (and gives a companion warning [[SC1079]] at that spot).
@@ -20,12 +24,16 @@ ShellCheck warns when it detects multi-line double quoted, single quoted or back
If you do want a multiline variable, just make sure the character after it is a quote, space or line feed.
var='multiline
'value
```sh
var='multiline
'value
```
can be rewritten for readability and to remove the warning:
var='multiline
value'
```sh
var='multiline
value'
```
As always `` `..` `` should be rewritten to ``$(..)``.
As always `` `..` `` should be rewritten to ``$(..)``.

@@ -1,3 +1,3 @@
## This is actually an end quote, but due to next char it looks suspect.
See companion warning [[SC1078]].
See companion warning [[SC1078]].

@@ -2,17 +2,21 @@
### Problematic code:
If true
Then
echo "hello"
Fi
```sh
If true
Then
echo "hello"
Fi
```
### Correct code:
if true
then
echo "hello"
fi
```sh
if true
then
echo "hello"
fi
```
### Rationale:
@@ -20,4 +24,4 @@ Shells are case sensitive and do not accept `If` or `IF` in place of lowercase `
### Exceptions
If you're aware of this and insist on naming a function `WHILE`, you can quote the name to prevent shellcheck from thinking you meant `while`.
If you're aware of this and insist on naming a function `WHILE`, you can quote the name to prevent shellcheck from thinking you meant `while`.

@@ -2,26 +2,33 @@
### Problematic code:
rmf() { rm -f "$@" }
```sh
rmf() { rm -f "$@" }
```
or
eval echo \${foo}
```sh
eval echo \${foo}
```
### Correct code:
rmf() { rm -f "$@"; }
```sh
rmf() { rm -f "$@"; }
```
and
eval "echo \${foo}"
```sh
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 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.
@@ -29,4 +36,5 @@ ShellCheck does not warn about `{}`, since this is frequently used with `find` a
### 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 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}'`.
```

@@ -2,18 +2,22 @@
### Problematic code:
!#/bin/sh
echo "Hello World"
```sh
!#/bin/sh
echo "Hello World"
```
### Correct code:
#!/bin/sh
echo "Hello World"
```sh
#!/bin/sh
echo "Hello World"
```
### Rationale:
The shebang has been accidentally swapped. The `#` should come first: `#!`, not `!#`.
The shebang has been accidentally swapped. The `#` should come first: `#!`, not `!#`.
### Exceptions
None.
None.

@@ -2,17 +2,21 @@
### Problematic code:
for $var in *
do
echo "$var"
done
```sh
for $var in *
do
echo "$var"
done
```
### Correct code:
for var in *
do
echo "$var"
done
```sh
for var in *
do
echo "$var"
done
```
### Rationale:
@@ -22,4 +26,4 @@ The `for` loop expects the variable's name, not its value (and the name can not
### Exceptions
None.
None.

@@ -2,11 +2,15 @@
### Problematic code:
echo "$array[@]"
```sh
echo "$array[@]"
```
### Correct code:
echo "${array[@]}"
```sh
echo "${array[@]}"
```
### Rationale:
@@ -16,4 +20,4 @@ Curly braces are needed to tell the shell that the square brackets are part of t
### Exceptions
If you want the square brackets to be treated literally or as a glob, you can use `${var}[idx]` to prevent this warning.
If you want the square brackets to be treated literally or as a glob, you can use `${var}[idx]` to prevent this warning.

@@ -2,19 +2,27 @@
### Problematic code:
grep ^(.*)\1$ file
```sh
grep ^(.*)\1$ file
```
or
var = myfunction(value)
```sh
var = myfunction(value)
```
### Correct code:
grep '^(.*)\1$' file
```sh
grep '^(.*)\1$' file
```
or
var=$(myfunction value)
```sh
var=$(myfunction value)
```
### Rationale:
@@ -35,4 +43,4 @@ If you are trying to use parentheses for shell syntax, look up the actual syntax
### Exceptions:
None.
None.

@@ -2,18 +2,22 @@
### Problematic code:
if true
then
echo hello
fi
fi
```sh
if true
then
echo hello
fi
fi
```
### Correct code:
if true
then
echo hello
fi
```sh
if true
then
echo hello
fi
```
### Rationale:
@@ -21,14 +25,16 @@ This error is typically seen when there are too many `fi`, `done` or `esac`s, or
In some cases, it can even be caused by bad quoting:
var="foo
if [[ $var = "bar ]
then
echo true
fi
```sh
var="foo
if [[ $var = "bar ]
then
echo true
fi
```
In this case, the `if` ends up inside the double quotes, leaving the `then` dangling.
### Exceptions:
None.
None.

@@ -2,19 +2,23 @@
### Problematic code:
. "$(find_install_dir)/lib.sh"
```sh
. "$(find_install_dir)/lib.sh"
```
### Correct code:
# shellcheck source=src/lib.sh
. "$(find_install_dir)/lib.sh"
```sh
# 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.
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`.
If you don't care that ShellCheck is unable to account for the file, specify `# shellcheck source=/dev/null`.

@@ -4,12 +4,16 @@ Reasons include: file not found, no permissions, not included on the command lin
### Problematic code:
source somefile
```sh
source somefile
```
### Correct code:
# shellcheck disable=SC1091
source somefile
```sh
# shellcheck disable=SC1091
source somefile
```
### Rationale:
@@ -21,4 +25,4 @@ Feel free to ignore the error with a [[directive]].
### Exceptions:
If you're fine with it, ignore the message with a [[directive]].
If you're fine with it, ignore the message with a [[directive]].

@@ -2,12 +2,16 @@
### Problematic code:
source mylib
```sh
source mylib
```
### Correct code:
# shellcheck disable=SC1094
source mylib
```sh
# shellcheck disable=SC1094
source mylib
```
(or fix `mylib`)
@@ -19,4 +23,4 @@ Fix parsing error, or just disable it with a directive.
### Exceptions:
If the file is fine and this is due to a known `shellcheck` bug, you can ignore it with a [[directive]] as in the example.
If the file is fine and this is due to a known `shellcheck` bug, you can ignore it with a [[directive]] as in the example.

@@ -2,17 +2,23 @@
### Problematic code:
var==value
```sh
var==value
```
### Correct code:
Assignment:
var=value
```sh
var=value
```
Comparison:
[ "$var" = value ]
```sh
[ "$var" = value ]
```
### Rationale:
@@ -26,4 +32,6 @@ ShellCheck has noticed that you're using `==` in an unexpected way. The two most
If you wanted to assign a literal equals sign, use quotes to make this clear:
var="=sum(A1:A10)"
```sh
var="=sum(A1:A10)"
```

@@ -2,11 +2,15 @@
### Problematic code:
eval $var=(a b)
```sh
eval $var=(a b)
```
### Correct code:
eval "$var=(a b)"
```sh
eval "$var=(a b)"
```
### Rationale:
@@ -21,4 +25,4 @@ Since the expression is evaluated as shell script code anyways, it should be pas
### Exceptions:
None.
None.

@@ -2,22 +2,26 @@
### Problematic code:
while sleep 1
do# show time
date
done
```sh
while sleep 1
do# show time
date
done
```
### Correct code:
while sleep 1
do # show time
date
done
```sh
while sleep 1
do # show time
date
done
```
### Rationale:
ShellCheck has noticed that you have a keyword immediately followed by a `#`. In order for the `#` to start a comment, it needs to come after a word boundary such as a space.
ShellCheck has noticed that you have a keyword immediately followed by a `#`. In order for the `#` to start a comment, it needs to come after a word boundary such as a space.
### Exceptions:
None.
None.

@@ -2,11 +2,15 @@
### Problematic code:
string="stirng" ; echo "$string" | sed -e "s/ir/ri/"
```sh
string="stirng" ; echo "$string" | sed -e "s/ir/ri/"
```
### Correct code:
string="stirng" ; echo "${string//ir/ri}"
```sh
string="stirng" ; echo "${string//ir/ri}"
```
### Rationale:
@@ -16,6 +20,8 @@ Let's assume somewhere earlier in your code you have put data into a variable (E
Occasionally a more complex sed substitution is required. For example, getting the last character of a string.
string="stirng" ; echo "$string" | sed -e "s/^.*\(.\)$/\1/"
```sh
string="stirng" ; echo "$string" | sed -e "s/^.*\(.\)$/\1/"
```
This is a bit simple for the example and there are alternative ways of doing this in the shell, but this SC2001 flags on several of my crazy complex sed commands which are beyond the scope of this example. Utilizing some of the more complex capabilities of sed is required occasionally and it is safe to ignore SC2001.

@@ -16,7 +16,7 @@ cat file | ( while read i; do echo "${i%?}"; done )
### Rationale:
`cat` is a tool for con"cat"enating files. Reading a single file as input to a program is considered a [Useless Use Of Cat (UUOC)](http://en.wikipedia.org/wiki/Cat_(Unix)#Useless_use_of_cat).
`cat` is a tool for con"cat"enating files. Reading a single file as input to a program is considered a [Useless Use Of Cat (UUOC)](http://en.wikipedia.org/wiki/Cat_(Unix)#Useless_use_of_cat).
It's more efficient and less roundabout to simply use redirection. This is especially true for programs that can benefit from seekable input, like `tail` or `tar`.
@@ -24,4 +24,4 @@ Many tools also accept optional filenames, e.g. `grep -q foo file` instead of `c
### Exceptions
None.
None.

@@ -2,13 +2,17 @@
### Problematic code:
i=$(expr 1 + 2)
l=$(expr length "$var")
```sh
i=$(expr 1 + 2)
l=$(expr length "$var")
```
### Correct code:
i=$((1+2))
l=${#var}
```sh
i=$((1+2))
l=${#var}
```
### Rationale:
@@ -18,8 +22,8 @@
### Exceptions
`sh` doesn't have a great replacement for the `:` operator (regex match). ShellCheck tries not to warn when using expr with `:`, but e.g. `op=:; expr string "$op" regex` still trigger it.
`sh` doesn't have a great replacement for the `:` operator (regex match). ShellCheck tries not to warn when using expr with `:`, but e.g. `op=:; expr string "$op" regex` still trigger it.
Other than that, all uses of `expr` can be rewritten to use modern shell features instead.
Other than that, all uses of `expr` can be rewritten to use modern shell features instead.
Bash has `[[ string =~ regex ]]`, so not even `expr .. : ..` is necessary.
Bash has `[[ string =~ regex ]]`, so not even `expr .. : ..` is necessary.

@@ -2,20 +2,26 @@
### Problematic code:
echo $(($n+1))
```sh
echo $(($n+1))
```
### Correct code:
echo $((n+1))
```sh
echo $((n+1))
```
### Rationale:
The `$` on regular variables in arithmetic contexts is unnecessary, and can even lead to subtle bugs. This is because the contents of `$((..))` is first expanded into a string, and then evaluated as an expression:
$ a='1+1'
$ echo $(($a * 5)) # becomes 1+1*5
6
$ echo $((a * 5)) # evaluates as (1+1)*5
10
```sh
$ a='1+1'
$ echo $(($a * 5)) # becomes 1+1*5
6
$ echo $((a * 5)) # evaluates as (1+1)*5
10
```
The `$` is unavoidable for special variables like `$1` vs `1`, `$#` vs `#`. ShellCheck does not warn about these cases.
The `$` is unavoidable for special variables like `$1` vs `1`, `$#` vs `#`. ShellCheck does not warn about these cases.

@@ -1,12 +1,16 @@
# Use $(..) instead of legacy \`..\`
# Use $(..) instead of legacy \`..\`
### Problematic code
echo "Current time: `date`"
```sh
echo "Current time: `date`"
```
### Correct code
echo "Current time: $(date)"
```sh
echo "Current time: $(date)"
```
### Rationale
@@ -14,7 +18,7 @@ Backtick command substitution `` `..` `` is legacy syntax with several issues.
1. It has a series of undefined behaviors related to quoting in POSIX.
1. It imposes a custom escaping mode with surprising results.
1. It's exceptionally hard to nest.
1. It's exceptionally hard to nest.
`$(..)` command substitution has none of these problems, and is therefore strongly encouraged.
@@ -24,4 +28,4 @@ None.
### See also
- http://mywiki.wooledge.org/BashFAQ/082
- http://mywiki.wooledge.org/BashFAQ/082

@@ -2,18 +2,22 @@
### Problematic code:
find . | echo
```sh
find . | echo
```
### Correct code:
find .
```sh
find .
```
### Rationale:
You are piping command output to `echo`, but `echo` ignores all piped input.
In particular, `echo` is not responsible for putting output on screen. Commands already output data, and with no further actions that will end up on screen.
In particular, `echo` is not responsible for putting output on screen. Commands already output data, and with no further actions that will end up on screen.
### Exceptions:
None.
None.

@@ -2,11 +2,15 @@
### Problematic Code:
ps ax | grep -v grep | grep "$service" > /dev/null
```sh
ps ax | grep -v grep | grep "$service" > /dev/null
```
### Correct Code:
pgrep -f "$service" > /dev/null
```sh
pgrep -f "$service" > /dev/null
```
### Rationale:
@@ -16,11 +20,15 @@ If you are just after a pid from a running program, then pgrep is a much safer a
What if you have the pid and you are looking for the matching program name?
pid=123; ps ax | grep "$pid"
```sh
pid=123; ps ax | grep "$pid"
```
What if you want a range of the ps field, like from the 16th space to the end of the line?
ps ax | grep "$pid" | cut -d" " -f16-
```sh
ps ax | grep "$pid" | cut -d" " -f16-
```
Both are valid cases where SC2009 is not valid.

@@ -1 +1 @@
## Don't use ls | grep. Use a glob or a for loop with a condition to allow non-alphanumeric filenames.
## Don't use ls | grep. Use a glob or a for loop with a condition to allow non-alphanumeric filenames.

@@ -2,11 +2,15 @@
### Problematic code:
ls -l | grep " $USER " | grep '\.txt$'
```sh
ls -l | grep " $USER " | grep '\.txt$'
```
### Correct code:
find . -maxdepth 1 -name '*.txt' -user "$USER"
```sh
find . -maxdepth 1 -name '*.txt' -user "$USER"
```
### Rationale:
@@ -14,18 +18,20 @@
Here's an example:
$ ls -l
total 0
-rw-r----- 1 me me 0 Feb 5 20:11 foo?bar
-rw-r----- 1 me me 0 Feb 5 2011 foo?bar
-rw-r----- 1 me me 0 Feb 5 20:11 foo?bar
```sh
$ ls -l
total 0
-rw-r----- 1 me me 0 Feb 5 20:11 foo?bar
-rw-r----- 1 me me 0 Feb 5 2011 foo?bar
-rw-r----- 1 me me 0 Feb 5 20:11 foo?bar
```
It shows three seemingly identical filenames, and did you spot the time format change? How it formats and what it redacts can differ between locale settings, `ls` version, and whether output is a tty.
It shows three seemingly identical filenames, and did you spot the time format change? How it formats and what it redacts can differ between locale settings, `ls` version, and whether output is a tty.
`ls` can usually be substituted for `find` if it's the filenames you're after.
If trying to parse out any other fields, first see whether `stat` (GNU, OS X, FreeBSD) or `find -printf` (GNU) can give you the data you want directly.
If trying to parse out any other fields, first see whether `stat` (GNU, OS X, FreeBSD) or `find -printf` (GNU) can give you the data you want directly.
### Exceptions:
If the information is intended for the user and not for processing (`ls -l ~/dir | nl; echo "Ok to delete these files?"`) you can ignore this error with a [[directive]].
If the information is intended for the user and not for processing (`ls -l ~/dir | nl; echo "Ok to delete these files?"`) you can ignore this error with a [[directive]].

@@ -2,24 +2,30 @@
### Problematic code:
for line in $(cat file | grep -v '^ *#')
do
echo "Line: $line"
done
```sh
for line in $(cat file | grep -v '^ *#')
do
echo "Line: $line"
done
```
### Correct code:
grep -v '^ *#' < file | while IFS= read -r line
do
echo "Line: $line"
done
```sh
grep -v '^ *#' < file | while IFS= read -r line
do
echo "Line: $line"
done
```
or without a subshell (bash, zsh, ksh):
while IFS= read -r line
do
echo "Line: $line"
done < <(grep -v '^ *#' < file)
```sh
while IFS= read -r line
do
echo "Line: $line"
done < <(grep -v '^ *#' < file)
```
### Rationale:
@@ -27,24 +33,30 @@ For loops by default (subject to `$IFS`) read word by word. Additionally, glob e
Given this text file:
foo *
bar
```sh
foo *
bar
```
The for loop will print:
Line: foo
Line: aardwark.jpg
Line: bullfrog.jpg
...
```sh
Line: foo
Line: aardwark.jpg
Line: bullfrog.jpg
...
```
The while loop will print:
Line: foo *
Line: bar
```sh
Line: foo *
Line: bar
```
### Exceptions
If you want to read word by word, you should still use a while read loop (e.g. with `read -a` to read words into an array).
Rare reasons for ignoring this message is if you don't care because your file only contains numbers and you're not interested in good practices, or if you've set `$IFS` appropriately and also disabled globbing.
Rare reasons for ignoring this message is if you don't care because your file only contains numbers and you're not interested in good practices, or if you've set `$IFS` appropriately and also disabled globbing.

@@ -2,23 +2,29 @@
### Problematic code:
find . -name '*.tar' -exec tar xf {} -C "$(dirname {})" \;
```sh
find . -name '*.tar' -exec tar xf {} -C "$(dirname {})" \;
```
### Correct code:
find . -name '*.tar' -exec sh -c 'tar xf "$1" -C "$(dirname "$1")"' _ {} \;
```sh
find . -name '*.tar' -exec sh -c 'tar xf "$1" -C "$(dirname "$1")"' _ {} \;
```
### Rationale:
Bash evaluates any command substitutions before the command they feature in is executed. In this case, the command is `find`. This means that `$(dirname {})` will run **before** `find` runs, and not **while** `find` runs.
Bash evaluates any command substitutions before the command they feature in is executed. In this case, the command is `find`. This means that `$(dirname {})` will run **before** `find` runs, and not **while** `find` runs.
To run shell code for each file, we can write a tiny script and inline it with `sh -c`. We add `_` as a dummy argument that becomes `$0`, and a filename argument that becomes `$1` in the inlined script:
$ sh -c 'echo "$1 is in $(dirname "$1")"' _ "mydir/myfile"
mydir/myfile is in mydir
```sh
$ sh -c 'echo "$1 is in $(dirname "$1")"' _ "mydir/myfile"
mydir/myfile is in mydir
```
This command can be executed by `find -exec`, with `{}` as the filename argument. It executes shell which interprets the inlined script once for each file. Note that the inlined script is single quoted, again to ensure that the expansion does not happen prematurely .
This command can be executed by `find -exec`, with `{}` as the filename argument. It executes shell which interprets the inlined script once for each file. Note that the inlined script is single quoted, again to ensure that the expansion does not happen prematurely .
### Exceptions:
If you don't care (or if you prefer) that it's only expanded once, like when dynamically selecting the executable to be used by all invocations, you can ignore this message.
If you don't care (or if you prefer) that it's only expanded once, like when dynamically selecting the executable to be used by all invocations, you can ignore this message.

@@ -2,16 +2,20 @@
### Problematic code:
[[ $dryrun ]] && echo "Would delete file" || rm file
```sh
[[ $dryrun ]] && echo "Would delete file" || rm file
```
### Correct code:
if [[ $dryrun ]]
then
echo "Would delete file"
else
rm file
fi
```sh
if [[ $dryrun ]]
then
echo "Would delete file"
else
rm file
fi
```
### Rationale:
@@ -19,11 +23,11 @@ It's common to use `A && B` to run `B` when `A` is true, and `A || C` to run `C`
However, combining them into `A && B || C` is not the same as `if A then B else C`.
In this case, if `A` is true but `B` is false, `C` will run.
In this case, if `A` is true but `B` is false, `C` will run.
For the code sample above, if the script was run with stdout closed for any reason (such as explicitly running `script --dryrun >&-`), echo would fail and the file would be deleted, even though `$dryrun` was set!
If an `if` clause is used instead, this problem is avoided.
### Exceptions
Ignore this warning when you actually do intend to run C when either A or B fails.
Ignore this warning when you actually do intend to run C when either A or B fails.

@@ -2,13 +2,17 @@
### Problematic code:
name=World
echo 'Hello $name'
```sh
name=World
echo 'Hello $name'
```
### Correct code:
name=World
echo "Hello $name"
```sh
name=World
echo "Hello $name"
```
### Rationale:
@@ -18,10 +22,12 @@ 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"
```sh
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.
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".
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".

@@ -2,20 +2,24 @@
### Problematic code:
percent=$((count/total*100))
```sh
percent=$((count/total*100))
```
### Correct code:
percent=$((count*100/total))
```sh
percent=$((count*100/total))
```
### Rationale:
If integer division is performed before multiplication, the intermediate result will be truncated causing a loss of precision.
If integer division is performed before multiplication, the intermediate result will be truncated causing a loss of precision.
In this case, if `count=1` and `total=2`, then the problematic code results in `percent=0`, while the correct code gives `percent=50`.
### Exceptions:
If you want and expect truncation you can ignore this message.
If you want and expect truncation you can ignore this message.
ShellCheck doesn't warn when `b` and `c` are identical expressions, e.g. `a/10*10`, under the assumption that the intent is to rounded to the nearest 10 rather than the no-op of multiply by `1`.
ShellCheck doesn't warn when `b` and `c` are identical expressions, e.g. `a/10*10`, under the assumption that the intent is to rounded to the nearest 10 rather than the no-op of multiply by `1`.

@@ -2,20 +2,24 @@
### Problematic code:
echo 'hello world' | tr 'hello' 'goodbye'
```sh
echo 'hello world' | tr 'hello' 'goodbye'
```
### Correct code:
echo 'hello world' | sed -e 's/hello/goodbye/g'
```sh
echo 'hello world' | sed -e 's/hello/goodbye/g'
```
### Rationale:
`tr` is for `tr`ansliteration, turning some characters into other characters. It doesn't match strings or words, only individual characters.
`tr` is for `tr`ansliteration, turning some characters into other characters. It doesn't match strings or words, only individual characters.
In this case, it transliterates h->g, e->o, l->d, o->y, resulting in the string "goddb wbrdd" instead of "goodbye world".
The solution is to use a tool that does string search and replace, such as sed.
The solution is to use a tool that does string search and replace, such as sed.
### Exceptions
None.
None.

@@ -1,22 +1,26 @@
# Note that unlike globs, o* here matches 'ooo' but not 'oscar'
### Problematic code:
grep 'foo*'
```sh
grep 'foo*'
```
when wanting to match `food` and `foosball`, but not `mofo` or `keyfob`.
when wanting to match `food` and `foosball`, but not `mofo` or `keyfob`.
### Correct code:
grep '^foo'
```sh
grep '^foo'
```
### Rationale:
As a glob, `foo*` means "Any string starting with foo", e.g. `food` and `foosball`.
As a regular expression, "foo*" means "f followed by 1 or more o's, anywhere", e.g. "mofo" or "keyfob".
As a regular expression, "foo*" means "f followed by 1 or more o's, anywhere", e.g. "mofo" or "keyfob".
This construct is way more common as a glob than as a regex, so ShellCheck notifies you about it.
### Exceptions
If you're aware of the above, you can ignore this message. If you'd like shellcheck to be quiet, use a [[directive]] or `'fo[o]*'`.
If you're aware of the above, you can ignore this message. If you'd like shellcheck to be quiet, use a [[directive]] or `'fo[o]*'`.

@@ -2,11 +2,11 @@
### Problematic code:
sudo echo 'export FOO=bar' >> /etc/profile
sudo echo 'export FOO=bar' >> /etc/profile
### Correct code:
echo 'export FOO=bar' | sudo tee -a /etc/profile > /dev/null
echo 'export FOO=bar' | sudo tee -a /etc/profile > /dev/null
### Rationale:
@@ -18,15 +18,15 @@ There is nothing special about `tee`. It's just the simplest command that can bo
Truncating:
echo 'data' | sudo dd of=file
echo 'data' | sudo sed 'w file'
echo 'data' | sudo dd of=file
echo 'data' | sudo sed 'w file'
Appending:
echo 'data' | sudo awk '{ print $0 >> "file" }'
echo 'data' | sudo sh -c 'cat >> file'
echo 'data' | sudo awk '{ print $0 >> "file" }'
echo 'data' | sudo sh -c 'cat >> file'
### Exceptions
If you want to run a command as root but redirect as the normal user, you can ignore this message.
If you want to run a command as root but redirect as the normal user, you can ignore this message.

@@ -2,11 +2,15 @@
### Problematic code:
PS1='\e[36m\$ \e(B\e[m'
```sh
PS1='\e[36m\$ \e(B\e[m'
```
### Correct code:
PS1='\[\e[36m\]\$ \[\e(B\e[m\]'
```sh
PS1='\[\e[36m\]\$ \[\e(B\e[m\]'
```
### Rationale:
@@ -16,4 +20,4 @@ Note: ShellCheck offers this as a helpful hint and not a robust check. Don't rel
### Exceptions
None.
None.

@@ -2,36 +2,46 @@
### Problematic code:
alias server_uptime='ssh $host 'uptime -p''
```sh
alias server_uptime='ssh $host 'uptime -p''
```
### Correct code:
alias server_uptime='ssh $host '"'uptime -p'"
```sh
alias server_uptime='ssh $host '"'uptime -p'"
```
### Rationale:
In the first case, the user has four single quotes on a line, wishfully hoping that the shell will match them up as outer quotes around a string with literal single quotes:
# v--------match--------v
alias server_uptime='ssh $host 'uptime -p''
# ^--match--^
```sh
# v--------match--------v
alias server_uptime='ssh $host 'uptime -p''
# ^--match--^
```
The shell, meanwhile, always terminates single quoted strings at the first possible single quote:
# v---match--v
alias server_uptime='ssh $host 'uptime -p''
# ^^
```sh
# v---match--v
alias server_uptime='ssh $host 'uptime -p''
# ^^
```
Which is the same thing as `alias server_uptime='ssh $host uptime' -p`.
There is no way to nest single quotes. However, single quotes can be placed literally in double quotes, so we can instead concatenate a single quoted string and a double quoted string:
# v--match---v
alias server_uptime='ssh $host '"'uptime -p'"
# ^---match---^
```sh
# v--match---v
alias server_uptime='ssh $host '"'uptime -p'"
# ^---match---^
```
This results in an alias with embedded single quotes.
This results in an alias with embedded single quotes.
### Exceptions
None.
None.

@@ -2,11 +2,15 @@
### Problematic code:
echo "You enter "$HOSTNAME". You can smell the wumpus." >> /etc/issue
```sh
echo "You enter "$HOSTNAME". You can smell the wumpus." >> /etc/issue
```
### Correct code:
echo "You enter $HOSTNAME. You can smell the wumpus." >> /etc/issue
```sh
echo "You enter $HOSTNAME. You can smell the wumpus." >> /etc/issue
```
### Rationale:
@@ -14,12 +18,14 @@ Always quoting variables and command expansions is good practice, but blindly pu
In this case, ShellCheck has noticed that the quotes around the expansion are unquoting it, because the left quote is terminating an existing double quoted string, while the right quote starts a new one:
echo "You enter "$HOSTNAME". You can smell the wumpus."
|----------| |---------------------------|
Quoted No quotes Quoted
```sh
echo "You enter "$HOSTNAME". You can smell the wumpus."
|----------| |---------------------------|
Quoted No quotes Quoted
```
If the quotes were supposed to be literal, they should be escaped. If the quotes were supposed to quote an expansion (as in the example), they should be removed because this is already a double quoted string.
### Exceptions
None.
None.

@@ -1,25 +1,31 @@
# echo won't expand escape sequences. Consider printf.
# echo won't expand escape sequences. Consider printf.
### Problematic code:
echo "Name:\t$value"
```sh
echo "Name:\t$value"
```
### Correct code:
printf "Name:\t%s\n" "$value"
```sh
printf "Name:\t%s\n" "$value"
```
### Rationale:
Backslash escapes like `\t` and `\n` are not expanded by echo, and become literal backslash-t, backslash-n.
Backslash escapes like `\t` and `\n` are not expanded by echo, and become literal backslash-t, backslash-n.
`printf` does expand these sequences, and should be used instead.
Other, non-portable methods include `echo -e '\t'` and `echo $'\t'`. ShellCheck will warn if this is used in a script with shebang `#!/bin/sh`.
If you actually wanted a literal backslash-t, use
If you actually wanted a literal backslash-t, use
echo "\\t"
```sh
echo "\\t"
```
### Exceptions
None
None

@@ -2,21 +2,29 @@
### Problematic code:
ssh host "echo $HOSTNAME"
```sh
ssh host "echo $HOSTNAME"
```
### Correct code:
ssh host "echo \$HOSTNAME"
```sh
ssh host "echo \$HOSTNAME"
```
or
ssh host 'echo $HOSTNAME'
```sh
ssh host 'echo $HOSTNAME'
```
### Rationale:
Bash expands all arguments that are not escaped/singlequoted. This means that the problematic code is identical to
ssh host "echo clienthostname"
```sh
ssh host "echo clienthostname"
```
and will print out the client's hostname, not the server's hostname.
@@ -24,6 +32,6 @@ By escaping the `$` in `$HOSTNAME`, it will be transmitted literally and evaluat
### Exceptions
If you do want your string expanded on the client side, you can safely ignore this message.
If you do want your string expanded on the client side, you can safely ignore this message.
Keep in mind that the expanded string will be evaluated again on the server side, so for arbitrary variables and command output, you may need to add a layer of escaping with e.g. `printf %q`.
Keep in mind that the expanded string will be evaluated again on the server side, so for arbitrary variables and command output, you may need to add a layer of escaping with e.g. `printf %q`.

@@ -1,3 +1,3 @@
# Modification of var is local (to subshell caused by pipeline).
See companion warning [[SC2031]].
See companion warning [[SC2031]].

@@ -26,39 +26,51 @@ In `sh`, a temp file (better if fifo or fd) can be used instead of process subst
Variables set in subshells are not available outside the subshell. This is a wide topic, and better described on the [Wooledge Bash Wiki](http://mywiki.wooledge.org/BashFAQ/024).
Here are some constructs that cause subshells (shellcheck may not warn about all of them). In each case, you can replace `subshell1` by a command or function that sets a variable, e.g. simply `var=foo`, and the variable will appear to be unset after the command is run. Similarly, you can replace `regular` with `var=foo`, and it will be set afterwards:
Pipelines:
subshell1 | subshell2 | subshell3 # Bash, Dash, Ash
subshell1 | subshell2 | regular # Ksh, Zsh
```sh
subshell1 | subshell2 | subshell3 # Bash, Dash, Ash
subshell1 | subshell2 | regular # Ksh, Zsh
```
Command substitution:
regular "$(subshell1)" "`subshell2`"
```sh
regular "$(subshell1)" "`subshell2`"
```
Process substitution:
Process substitution:
regular <(subshell1) >(subshell2)
```sh
regular <(subshell1) >(subshell2)
```
Some forms of grouping:
( subshell )
{ regular; }
```sh
( subshell )
{ regular; }
```
Backgrounding:
subshell1 &
subshell2 &
```sh
subshell1 &
subshell2 &
```
Anything executed by external processes:
find . -exec subshell1 {} \;
find . -print0 | xargs -0 subshell2
sudo subshell3
su -c subshell4
```sh
find . -exec subshell1 {} \;
find . -print0 | xargs -0 subshell2
sudo subshell3
su -c subshell4
```
This applies not only to setting variables, but also setting shell options and changing directories.
This applies not only to setting variables, but also setting shell options and changing directories.
### Exceptions
You can ignore this error if you don't care that the changes aren't reflected, because work on the value branches and shouldn't be recombined.
You can ignore this error if you don't care that the changes aren't reflected, because work on the value branches and shouldn't be recombined.

@@ -1,3 +1,3 @@
# Use own script or sh -c '..' to run this from su.
See [[SC2033]].
See [[SC2033]].

@@ -2,16 +2,20 @@
### Problematic code:
foo() { bar --baz "$@"; frob --baz "$@"; };
find . -exec foo {} +
```sh
foo() { bar --baz "$@"; frob --baz "$@"; };
find . -exec foo {} +
```
### Correct code:
find . -exec sh -c 'bar --baz "$@"; frob --baz "$@";' -- {} +
```sh
find . -exec sh -c 'bar --baz "$@"; frob --baz "$@";' -- {} +
```
### Rationale:
Shell functions are only known to the shell. External commands like `find`, `xargs`, `su` and `sudo` do not recognize shell functions.
Shell functions are only known to the shell. External commands like `find`, `xargs`, `su` and `sudo` do not recognize shell functions.
Instead, the function contents can be executed in a shell, either through `sh -c` or by creating a separate shell script as an executable file.
@@ -19,7 +23,9 @@ Instead, the function contents can be executed in a shell, either through `sh -c
If you're intentionally passing a word that happens to have the same name as a declared function, you can quote it to make shellcheck ignore it, e.g.
nobody() {
sudo -u "nobody" "$@"
}
```sh
nobody() {
sudo -u "nobody" "$@"
}
```

@@ -2,17 +2,21 @@
### Problematic code:
foo=42
echo "$FOO"
```sh
foo=42
echo "$FOO"
```
### Correct code:
foo=42
echo "$foo"
```sh
foo=42
echo "$foo"
```
### Rationale:
Variables not used for anything are often associated with bugs, so ShellCheck warns about them.
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`.
@@ -22,21 +26,27 @@ ShellCheck may not always realize that the variable is in use (especially with i
For throwaway variables, consider using `_` as a dummy:
read _ last _ zip _ _ <<< "$str"
echo "$last, $zip"
```sh
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"
```sh
# 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}"
```sh
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.
As always, there are ways to [[ignore]] this and other messages if they frequently get in your way.

@@ -2,17 +2,21 @@
### Problematic code:
sum=find | wc -l
```sh
sum=find | wc -l
```
### Correct code:
sum=$(find | wc -l)
```sh
sum=$(find | wc -l)
```
### Rationale:
The intention in this code was that `sum` would in some way get the value of the command `find | wc -l`.
However, `|` has precedence over the `=`, so the command is a two stage pipeline consisting of `sum=find` and `wc -l`.
However, `|` has precedence over the `=`, so the command is a two stage pipeline consisting of `sum=find` and `wc -l`.
`sum=find` is a plain string assignment. Since it happens by itself in an independent pipeline stage, it has no effect: it produces no output, and the variable disappears when the pipeline stage finishes. Because the assignment produces no output, `wc -l` will count 0 lines.
@@ -20,4 +24,4 @@ To instead actually assign a variable with the output of a command, command subs
### Exceptions:
None. This warning is triggered whenever the first stage of a pipeline is a single assignment, which is never correct.
None. This warning is triggered whenever the first stage of a pipeline is a single assignment, which is never correct.

@@ -2,11 +2,15 @@
### Problematic code:
find . -type f | xargs md5sum
```sh
find . -type f | xargs md5sum
```
### Correct code:
find . -type f -print0 | xargs -0 md5sum
```sh
find . -type f -print0 | xargs -0 md5sum
```
### Rationale:
@@ -14,4 +18,4 @@ By default, `xargs` interprets spaces and quotes in an unsafe and unexpected way
### Exceptions
None.
None.

@@ -1,8 +1,8 @@
## `#!/bin/sh` was specified, but *something* is not standard.
The shebang indicates that the script works with `/bin/sh`, but you are using non-standard features that may not be supported.
The shebang indicates that the script works with `/bin/sh`, but you are using non-standard features that may not be supported.
It may currently work for you, but it can or will fail on other OS, the same OS with different configurations or from different contexts (like initramfs/chroot), or different versions of the same OS, including future updates to your current system.
It may currently work for you, but it can or will fail on other OS, the same OS with different configurations or from different contexts (like initramfs/chroot), or different versions of the same OS, including future updates to your current system.
Either declare a specific shell like `#!/usr/bin/env bash` to make sure this shell is always used, or rewrite the script in a portable way.
@@ -143,4 +143,4 @@ reuse_quote "$@"
## Exception
Depends on what your expected POSIX shell providers would use.
Depends on what your expected POSIX shell providers would use.

@@ -4,4 +4,4 @@ The shebang indicates that the script works with `/bin/sh`, but you are using no
Specify `#!/usr/bin/env bash` to ensure that bash (or your shell of choice) will be used, or rewrite the script to be more portable.
The Ubuntu wiki has [a list of portability issues](https://wiki.ubuntu.com/DashAsBinSh) and suggestions on how to rewrite them.
The Ubuntu wiki has [a list of portability issues](https://wiki.ubuntu.com/DashAsBinSh) and suggestions on how to rewrite them.

@@ -2,17 +2,21 @@
### Problematic code:
for i in 'seq 1 10'
do
echo "$i"
done
```sh
for i in 'seq 1 10'
do
echo "$i"
done
```
### Correct code:
for i in $(seq 1 10)
do
echo "$i"
done
```sh
for i in $(seq 1 10)
do
echo "$i"
done
```
### Rationale:
@@ -22,4 +26,4 @@ This is one of the many problems with backticks, so it's better to use `$(..)` t
### Exceptions:
None.
None.

@@ -2,30 +2,40 @@
### Problematic code:
for var in value
do
echo "$var"
done
```sh
for var in value
do
echo "$var"
done
```
### Correct code:
Correct code depends on what you want to do.
Correct code depends on what you want to do.
To iterate over files in a directory, instead of `for var in /my/dir` use:
for var in /my/dir/* ; do echo "$var"; done
```sh
for var in /my/dir/* ; do echo "$var"; done
```
To iterate over lines in a file or command output, use a while read loop instead:
mycommand | while IFS= read -r line; do echo "$line"; done
```sh
mycommand | while IFS= read -r line; do echo "$line"; done
```
To iterate over *words* written to a command or function's stdout, instead of `for var in myfunction`, use
for var in $(myfunction); do echo "$var"; done
```sh
for var in $(myfunction); do echo "$var"; done
```
To iterate over *words* in a variable, instead of `for var in myvariable`, use
for var in $myvariable; do echo "$var"; done
```sh
for var in $myvariable; do echo "$var"; done
```
@@ -33,8 +43,8 @@ To iterate over *words* in a variable, instead of `for var in myvariable`, use
ShellCheck has detected that your for loop iterates over a single, constant value. This is most likely a bug in your code, caused by you not expanding the value in the way you want.
You should make sure that whatever you loop over will expand into multiple words.
You should make sure that whatever you loop over will expand into multiple words.
### Exceptions
None.
None.

100
SC2044.md

@@ -2,31 +2,35 @@
### Problematic code:
for file in $(find mydir -mtime -7 -name '*.mp3')
do
let count++
echo "Playing file no. $count"
play "$file"
done
echo "Played $count files"
```sh
for file in $(find mydir -mtime -7 -name '*.mp3')
do
let count++
echo "Playing file no. $count"
play "$file"
done
echo "Played $count files"
```
This will fail for filenames containing spaces and similar, such as `My File.mp3`, and has a series of potential globbing issues depending on other filenames in the directory like (if you have `MyFile2.mp3` and `MyFile[2014].mp3`, the former file will play twice and the latter will not play at all).
### Correct code:
There are many possible fixes, each with its pros and cons.
There are many possible fixes, each with its pros and cons.
The most general fix (that requires the least amount of thinking to apply) is having `find` output a `\0` separated list of files and consuming them in a `while read` loop:
while IFS= read -r -d '' file
do
let count++
echo "Playing file no. $count"
play "$file"
done < <(find mydir -mtime -7 -name '*.mp3' -print0)
echo "Played $count files"
```sh
while IFS= read -r -d '' file
do
let count++
echo "Playing file no. $count"
play "$file"
done < <(find mydir -mtime -7 -name '*.mp3' -print0)
echo "Played $count files"
```
In usage it's very similar to the `for` loop: it gets its output from a `find` statement, it executes a shell script body, it allows updating/aggregating variables, and the variables are available when the loop ends.
In usage it's very similar to the `for` loop: it gets its output from a `find` statement, it executes a shell script body, it allows updating/aggregating variables, and the variables are available when the loop ends.
It requires Bash, and works with GNU, Busybox, OS X, FreeBSD and OpenBSD find, but not POSIX find.
@@ -34,14 +38,16 @@ It requires Bash, and works with GNU, Busybox, OS X, FreeBSD and OpenBSD find, b
If you don't need `find` logic like `-mtime -7` and just use it to match globs recursively (all `*.mp3` files under a directory), you can instead use `globstar` and `nullglob` instead of `find`, and still use a `for` loop:
shopt -s globstar nullglob
for file in mydir/**/*.mp3
do
let count++
echo "Playing file no. $count"
play "$file"
done
echo "Played $count files"
```sh
shopt -s globstar nullglob
for file in mydir/**/*.mp3
do
let count++
echo "Playing file no. $count"
play "$file"
done
echo "Played $count files"
```
This is bash specific.
@@ -50,26 +56,30 @@ This is bash specific.
If you need POSIX compliance, this is a fair approach:
find mydir ! -name "$(printf "*\n*")" -name '*.mp3' > tmp
while IFS= read -r file
do
let count++
echo "Playing file #$count"
play "$file"
done < tmp
rm tmp
echo "Played $count files"
```sh
find mydir ! -name "$(printf "*\n*")" -name '*.mp3' > tmp
while IFS= read -r file
do
let count++
echo "Playing file #$count"
play "$file"
done < tmp
rm tmp
echo "Played $count files"
```
The only problem is for filenames containing line feeds. A `! -name "$(printf "*\n*")"` has been added to simply skip these files, just in case there are any.
The only problem is for filenames containing line feeds. A `! -name "$(printf "*\n*")"` has been added to simply skip these files, just in case there are any.
If you don't need variables to be available after the loop (here, if you don't need to print the final play count at the end), you can skip the `tmp` file and just pipe from `find` to `while`.
If you don't need variables to be available after the loop (here, if you don't need to print the final play count at the end), you can skip the `tmp` file and just pipe from `find` to `while`.
##### For simple commands with no aggregation
If you don't need a shell script loop body or any form of variable like if we only wanted to play the file, we can dramatically simplify while maintaining POSIX compatibility:
# Simple and POSIX
find mydir -name '*.mp3' -exec play {} \;
```sh
# Simple and POSIX
find mydir -name '*.mp3' -exec play {} \;
```
This does not allow things like `let counter++` because `let` is a shell builtin, not an external command.
@@ -77,10 +87,12 @@ This does not allow things like `let counter++` because `let` is a shell builtin
If we do need a shell script body but no aggregation, you can do the above but invoking `sh` (this is still POSIX):
find mydir -name '*.mp3' -exec sh -c '
echo "Playing ${1%.mp3}"
play "$1"
' sh {} \;
```sh
find mydir -name '*.mp3' -exec sh -c '
echo "Playing ${1%.mp3}"
play "$1"
' sh {} \;
```
This would not be possible without `sh`, because `${1%.mp3}` is a shell construct that `find` can't evaluate by itself. If we had tried to `let counter++` in this loop, we would have found that the value never changes.
@@ -89,10 +101,10 @@ Note that using `+` instead of `\;`, and using an embedded `for file in "$@"` lo
### Rationale:
`for var in $(find ...)` loops rely on word splitting and will evaluate globs, which will wreck havoc with filenames containing whitespace or glob characters.
`for var in $(find ...)` loops rely on word splitting and will evaluate globs, which will wreck havoc with filenames containing whitespace or glob characters.
`find -exec` `for i in glob` and `find`+`while` do not rely on word splitting, so they avoid this problem.
`find -exec` `for i in glob` and `find`+`while` do not rely on word splitting, so they avoid this problem.
### Exceptions
If you know about and carefully apply `IFS=$'\n'` and `set -f`, you could choose to ignore this message.
If you know about and carefully apply `IFS=$'\n'` and `set -f`, you could choose to ignore this message.

@@ -2,11 +2,15 @@
### Problematic code:
cp $* ~/dir
```sh
cp $* ~/dir
```
### Correct code:
cp "$@" ~/dir
```sh
cp "$@" ~/dir
```
### Rationale:
@@ -22,4 +26,4 @@ Since the latter is rarely expected or desired, ShellCheck warns about it.
### Exceptions
None.
None.

@@ -2,17 +2,17 @@
### Problematic code:
for i in {1..$n}
do
echo "$i"
done
for i in {1..$n}
do
echo "$i"
done
### Correct code:
for ((i=0; i<n; i++))
do
echo "$i"
done
for ((i=0; i<n; i++))
do
echo "$i"
done
### Rationale:
@@ -20,18 +20,18 @@ In Bash, brace expansion happens before variable expansion. This means that brac
For integers, use an arithmetic for loop instead. For zero-padded numbers or letters, use of eval may be warranted:
from="a" to="m"
for c in $(eval "echo {$from..$to}"); do echo "$c"; done
from="a" to="m"
for c in $(eval "echo {$from..$to}"); do echo "$c"; done
or more carefully (if `from`/`to` could be user input, or if the brace expansion could have spaces):
from="a" to="m"
while IFS= read -d '' -r c
do
echo "Read $c"
done < <(eval "printf '%s\0' $(printf "{%q..%q}.jpg" "$from" "$to")")
from="a" to="m"
while IFS= read -d '' -r c
do
echo "Read $c"
done < <(eval "printf '%s\0' $(printf "{%q..%q}.jpg" "$from" "$to")")
### Exceptions
None (if you're writing for e.g. zsh, make sure the shebang indicates this so shellcheck won't warn)
None (if you're writing for e.g. zsh, make sure the shebang indicates this so shellcheck won't warn)

@@ -2,17 +2,21 @@
### Problematic code:
if [[ $1 != foo || $1 != bar ]]
then
echo "$1 is not foo or bar"
fi
```sh
if [[ $1 != foo || $1 != bar ]]
then
echo "$1 is not foo or bar"
fi
```
### Correct code:
if [[ $1 != foo && $1 != bar ]]
then
echo "$1 is not foo or bar"
fi
```sh
if [[ $1 != foo && $1 != bar ]]
then
echo "$1 is not foo or bar"
fi
```
### Rationale:
@@ -34,4 +38,4 @@ This statement is identical to `! [[ $1 = foo || $1 = bar ]]`, which also works
### Exceptions
None.
None.

@@ -2,21 +2,27 @@
### Problematic code:
printf "Hello, $NAME\n"
```sh
printf "Hello, $NAME\n"
```
### Correct code:
printf "Hello, %s\n" "$NAME"
```sh
printf "Hello, %s\n" "$NAME"
```
### Rationale:
`printf` interprets escape sequences and format specifiers in the format string. If variables are included, any escape sequences or format specifiers in the data will be interpreted too, when you most likely wanted to treat it as data. Example:
coverage='96%'
printf "Unit test coverage: %s\n" "$coverage"
printf "Unit test coverage: $coverage\n"
```sh
coverage='96%'
printf "Unit test coverage: %s\n" "$coverage"
printf "Unit test coverage: $coverage\n"
```
The first printf writes `Unit test coverage: 96%`.
The first printf writes `Unit test coverage: 96%`.
The second writes ``bash: printf: `\': invalid format character``
@@ -24,7 +30,9 @@ The second writes ``bash: printf: `\': invalid format character``
Sometimes you may actually want to interpret data as a format string, like in:
hexToAscii() { printf "\x$1"; }
hexToAscii 21
```sh
hexToAscii() { printf "\x$1"; }
hexToAscii 21
```
Like all warnings, you can selectively silence this warning with a [directive](Directive).
Like all warnings, you can selectively silence this warning with a [directive](Directive).

@@ -2,11 +2,15 @@
### Problematic code:
tr -cd [:digit:]
```sh
tr -cd [:digit:]
```
### Correct code:
tr -cd '[:digit:]'
```sh
tr -cd '[:digit:]'
```
### Rationale:
@@ -14,4 +18,4 @@
### Exceptions
None
None

@@ -2,19 +2,23 @@
### Problematic code:
grep '*foo*'
```sh
grep '*foo*'
```
### Correct code:
grep 'foo' # or more explicitly, grep '.*foo.*'
```sh
grep 'foo' # or more explicitly, grep '.*foo.*'
```
### Rationale:
In globs, `*` matches any number of any character.
In regex, `*` matches any number of the preceding character.
In regex, `*` matches any number of the preceding character.
`grep` uses regex, not globs, so this means that `grep '*foo'` is nonsensical because there's no preceding character for `*`.
`grep` uses regex, not globs, so this means that `grep '*foo'` is nonsensical because there's no preceding character for `*`.
If the intention was to match "any number of characters followed by foo", use `'.*foo'`. Also note that since grep matches substrings, this will match "fishfood". Use anchors to prevent this, e.g. `foo$`.
@@ -23,4 +27,4 @@ This also means that `f*` will match "hello", because `f*` matches 0 (or more) "
### Exceptions
If you're aware of the differences between globs and regex, you can ignore this message.
If you're aware of the differences between globs and regex, you can ignore this message.

@@ -2,11 +2,15 @@
### Problematic code:
trap "echo \"Finished on $(date)\"" EXIT
```sh
trap "echo \"Finished on $(date)\"" EXIT
```
### Correct code:
trap 'echo "Finished on $(date)"' EXIT
```sh
trap 'echo "Finished on $(date)"' EXIT
```
### Rationale:
@@ -14,8 +18,8 @@ With double quotes, all parameter and command expansions will expand when the tr
In the example, the message will contain the date on which the trap was declared, and not the date on which the script exits.
Using single quotes will prevent expansion at declaration time, and save it for execution time.
Using single quotes will prevent expansion at declaration time, and save it for execution time.
### Exceptions
If you don't care that the trap code is expanded early because the commands/variables won't change during execution of the script, or because you want to use the current and not the future values, then you can ignore this message.
If you don't care that the trap code is expanded early because the commands/variables won't change during execution of the script, or because you want to use the current and not the future values, then you can ignore this message.

@@ -19,4 +19,4 @@ ShellCheck would guess that you don't want it in tests.
### Exceptions:
When it's among a continuous list of redirections at the end of a simple `test` command, it's more likely that
the user really meant to do a redirection. Or any other case that you mean to do that.
the user really meant to do a redirection. Or any other case that you mean to do that.

@@ -2,30 +2,40 @@
### Problematic code:
for s in "$(mycommand)"; do echo "$s"; done
```sh
for s in "$(mycommand)"; do echo "$s"; done
```
### Correct code:
The correct code depends on your intention. Let's say you're in a directory with the files `file.png` and `My cat.png`, and you want to loop over a command that outputs (or variable that contains):
hello world
My *.png
```sh
hello world
My *.png
```
#### Loop over each line without globbing (`hello world`, `My *.png`)
mycommand | while IFS= read -r s; do echo "$s"; done
```sh
mycommand | while IFS= read -r s; do echo "$s"; done
```
#### Loop over each word with globbing (`hello`, `world`, `My`, `file.png`, `My cat.png`):
# relies on the fact that IFS by default contains space-tab-linefeed
for s in $(mycommand); do echo "$s"; done
```sh
# relies on the fact that IFS by default contains space-tab-linefeed
for s in $(mycommand); do echo "$s"; done
```
#### Loop over each line with globbing (`hello world`, `My cat.png`)
# explicitly set IFS to contain only a line feed
IFS='
'
for s in $(mycommand); do echo "$s"; done
```sh
# explicitly set IFS to contain only a line feed
IFS='
'
for s in $(mycommand); do echo "$s"; done
```
### Rationale:
@@ -33,4 +43,4 @@ You get this warning because you have a loop that will only ever run exactly one
### Exceptions
None.
None.

@@ -2,30 +2,38 @@
### Problematic code:
find . -type f -exec shellcheck {} | wc -l \;
find . -exec echo {} ;
```sh
find . -type f -exec shellcheck {} | wc -l \;
find . -exec echo {} ;
```
### Correct code:
find . -type f -exec sh -c 'shellcheck "$1" | wc -l' -- {} \;
find . -exec echo {} \;
```sh
find . -type f -exec sh -c 'shellcheck "$1" | wc -l' -- {} \;
find . -exec echo {} \;
```
### Rationale:
`find -exec` is still subject to all normal shell rules, so all shell features like `|`, `||`, `&` and `&&` will apply to the `find` command itself, and not to the command you are trying to construct with `-exec`.
`find . -exec foo {} && bar {} \;` means run the command `find . -exec foo {}`, and if find is successful, run the command `bar "{}" ";"`.
`find . -exec foo {} && bar {} \;` means run the command `find . -exec foo {}`, and if find is successful, run the command `bar "{}" ";"`.
To instead go through each file and run `foo file && bar file` on it, invoke a shell that can interpret `&&`:
find . -exec sh 'foo "$1" && bar "$1"' -- {} \;
```sh
find . -exec sh 'foo "$1" && bar "$1"' -- {} \;
```
You can also use find `-a` instead of shell `&&`:
find . -exec foo {} \; -a -exec bar {} \;
```sh
find . -exec foo {} \; -a -exec bar {} \;
```
This will have the same effect (`-a` is also the default when two commands are specified, and can therefore be omitted).
### Exceptions
None
None

@@ -2,11 +2,15 @@
### Problematic code:
cp $@ ~/dir
```sh
cp $@ ~/dir
```
### Correct code:
cp "$@" ~/dir
```sh
cp "$@" ~/dir
```
### Rationale:
@@ -22,4 +26,4 @@ Since the latter is rarely expected or desired, ShellCheck warns about it.
### Exceptions
None.
None.

@@ -2,22 +2,26 @@
### Problematic code:
firefox 2>&1 > /dev/null
```sh
firefox 2>&1 > /dev/null
```
### Correct code:
firefox > /dev/null 2>&1
```sh
firefox > /dev/null 2>&1
```
### Rationale:
Redirections are handled in order.
Redirections are handled in order.
The problematic code means "Point stderr to where stdout is currently pointing (the terminal). Then point stdout to /dev/null".
The correct code means "Point stdout to /dev/null. Then point stderr to where stdout is currently pointing (/dev/null)".
In other words, the problematic code hides stdout and shows stderr. The correct code hides both stderr and stdout, which is usually the intention.
In other words, the problematic code hides stdout and shows stderr. The correct code hides both stderr and stdout, which is usually the intention.
### Exceptions
If you want stderr as stdout and stdout to a file, you can ignore this message.
If you want stderr as stdout and stdout to a file, you can ignore this message.

@@ -2,40 +2,48 @@
### Problematic code:
if [ -n $var ]
then
echo "var has a value"
else
echo "var is empty"
fi
```sh
if [ -n $var ]
then
echo "var has a value"
else
echo "var is empty"
fi
```
### Correct code:
In bash/ksh:
if [[ -n $var ]]
then
echo "var has a value"
else
echo "var is empty"
fi
```sh
if [[ -n $var ]]
then
echo "var has a value"
else
echo "var is empty"
fi
```
In POSIX:
if [ -n "$var" ]
then
echo "var has a value"
else
echo "var is empty"
fi
```sh
if [ -n "$var" ]
then
echo "var has a value"
else
echo "var is empty"
fi
```
### Rationale:
When `$var` is unquoted, a blank value will cause it to wordsplit and disappear. If `$var` is empty, these two statements are identical:
[ -n $var ]
[ -n ]
```sh
[ -n $var ]
[ -n ]
```
`[ string ]` is shorthand for testing if a string is empty. This is still true if `string` happens to be `-n`. `[ -n ]` is therefore true, and by extension so is `[ -n $var ]`.
@@ -43,4 +51,4 @@ To fix this, either use `[[ -n $var ]]` which has fewer caveats than `[`, or quo
### Exceptions:
None
None

@@ -2,12 +2,16 @@
### Problematic code:
[[ 2 -lt 3.14 ]]
```sh
[[ 2 -lt 3.14 ]]
```
### Correct code:
[[ 200 -lt 314 ]] # Use fixed point math
[[ $(echo "2 < 3.14" | bc) == 1 ]] # Use bc
```sh
[[ 200 -lt 314 ]] # Use fixed point math
[[ $(echo "2 < 3.14" | bc) == 1 ]] # Use bc
```
### Rationale:
@@ -15,4 +19,4 @@ Bash and Posix sh does not support decimals in numbers. Decimals should either b
### Exceptions
If the strings happen to be version numbers and you're using `<`, or `>` to compare them as strings, and you consider this an acceptable thing to do, then you can ignore this warning.
If the strings happen to be version numbers and you're using `<`, or `>` to compare them as strings, and you consider this an acceptable thing to do, then you can ignore this warning.

@@ -2,11 +2,15 @@
### Problematic code:
[[ $foo =~ "^fo+bar$" ]]
```sh
[[ $foo =~ "^fo+bar$" ]]
```
### Correct code:
[[ $foo =~ ^fo+bar$ ]]
```sh
[[ $foo =~ ^fo+bar$ ]]
```
### Rationale:
@@ -24,4 +28,4 @@ If you do want to match literally just to do a plain substring search, e.g. `[[
* In Bash 3.2 and newer with shopt `compat31` *enabled*, quoted and unquoted patterns match identically.
* In Bash 3.1 quoted and unquoted patterns match identically.
See http://stackoverflow.com/questions/218156/bash-regex-with-quotes
See http://stackoverflow.com/questions/218156/bash-regex-with-quotes

@@ -2,20 +2,24 @@
### Problematic code:
[[ 0=1 ]]
```sh
[[ 0=1 ]]
```
### Correct code:
[[ 0 = 1 ]]
```sh
[[ 0 = 1 ]]
```
### Rationale:
`[[ 0 = 1 ]]` means "check if 0 and 1 are equal".
`[[ str ]]` is short form for `[[ -n str ]]`, and means "check if `str` is non-empty". It doesn't matter if `str` happens to contain `0=1`.
`[[ str ]]` is short form for `[[ -n str ]]`, and means "check if `str` is non-empty". It doesn't matter if `str` happens to contain `0=1`.
Always use spaces around the comparison operator in `[..]` and `[[..]]`, otherwise it won't be recognized as an operator.
### Exceptions
None.
None.

@@ -2,46 +2,54 @@
### Problematic code:
var_1="hello world"
n=1
echo "${var_$n}"
```sh
var_1="hello world"
n=1
echo "${var_$n}"
```
### Correct code:
Bash/ksh:
# Use arrays instead of dynamic names
declare -a var
var[1]="hello world"
n=1
echo "${var[n]}"
```sh
# Use arrays instead of dynamic names
declare -a var
var[1]="hello world"
n=1
echo "${var[n]}"
```
or
# Expand variable names dynamically
var_1="hello world"
n=1
name="var_$n"
echo "${!name}"
```sh
# Expand variable names dynamically
var_1="hello world"
n=1
name="var_$n"
echo "${!name}"
```
POSIX sh:
# Expand dynamically with eval
var_1="hello world"
n=1
eval "tmp=\$var_$n"
echo "${tmp}"
```sh
# Expand dynamically with eval
var_1="hello world"
n=1
eval "tmp=\$var_$n"
echo "${tmp}"
```
### Rationale:
You can expand a variable `var_1` with `${var_1}`, but you can not generate the string `var_1` with an embedded expansion, like `${var_$n}`.
You can expand a variable `var_1` with `${var_1}`, but you can not generate the string `var_1` with an embedded expansion, like `${var_$n}`.
Instead, if at all possible, you should use an array. Bash and ksh support both numerical and associative arrays, and an example is shown above.
Instead, if at all possible, you should use an array. Bash and ksh support both numerical and associative arrays, and an example is shown above.
If you can't use arrays, you can indirectly reference variables by creating a temporary variable with its name, e.g. `myvar="var_$n"` and then expanding it indirectly with `${!myvar}`. This will give the contents of the variable `var_1`.
If you can't use arrays, you can indirectly reference variables by creating a temporary variable with its name, e.g. `myvar="var_$n"` and then expanding it indirectly with `${!myvar}`. This will give the contents of the variable `var_1`.
If using POSIX sh, where neither arrays nor `${!var}` is available, `eval` can be used. You must be careful in sanitizing the data used to construct the variable name to avoid arbitrary code execution.
### Exceptions:
None
None

@@ -2,42 +2,54 @@
### Problematic code:
i=4
$(( i++ ))
```sh
i=4
$(( i++ ))
```
### Correct code:
Bash, Ksh:
i=4
(( i++ ))
```sh
i=4
(( i++ ))
```
POSIX (assuming `++` is supported):
i=4
_=$(( i++ ))
```sh
i=4
_=$(( i++ ))
```
Alternative POSIX version that does not preserve the exit code:
: $(( i++ ))
```sh
: $(( i++ ))
```
### Rationale:
`$((..))` expands to a number. If it's the only word on the line, the shell will try to execute this number as a command name:
$ i=4
$ $(( i++ ))
4: command not found
$ echo $i
5
```sh
$ i=4
$ $(( i++ ))
4: command not found
$ echo $i
5
```
To avoid trying to execute the number as a command name, use one of the methods mentioned:
$ i=4
$ _=$(( i++ ))
$ echo $i
5
```sh
$ i=4
$ _=$(( i++ ))
$ echo $i
5
```
### Exceptions:
None.
None.

@@ -2,42 +2,51 @@
### Problematic code:
ssh host.example.com << EOF
echo "Logged in on $HOSTNAME"
EOF
```sh
ssh host.example.com << EOF
echo "Logged in on $HOSTNAME"
EOF
### Correct code:
ssh host.example.com << "EOF"
echo "Logged in on $HOSTNAME"
EOF
```sh
ssh host.example.com << "EOF"
echo "Logged in on $HOSTNAME"
EOF
```
### Rationale:
When the end token of a here document is unquoted, parameter expansion and command substitution will happen on in contents of the here doc.
When the end token of a here document is unquoted, parameter expansion and command substitution will happen on in contents of the here doc.
This means that before sending the commands to the server, the client replaces `$HOSTNAME` with localhost, thereby sending `echo "Logged in on localhost"` to the server. This has the effect of printing the client's hostname instead of the server's.
This means that before sending the commands to the server, the client replaces `$HOSTNAME` with localhost, thereby sending `echo "Logged in on localhost"` to the server. This has the effect of printing the client's hostname instead of the server's.
Scripts with any kind of variable use are especially problematic because all references will be expanded before the script run. For example,
ssh host << EOF
x="$(uname -a)"
echo "$x"
EOF
```sh
ssh host << EOF
x="$(uname -a)"
echo "$x"
EOF
```
will never print anything, neither client nor server details, since before evaluation, it will be expanded to:
x="Linux localhost ... x86_64 GNU/Linux"
echo ""
```sh
x="Linux localhost ... x86_64 GNU/Linux"
echo ""
```
By quoting the here token, local expansion will not take place, so the server sees `echo "Logged in on $HOSTNAME"` which is expanded and printed with the server's hostname, which is usually the intention.
By quoting the here token, local expansion will not take place, so the server sees `echo "Logged in on $HOSTNAME"` which is expanded and printed with the server's hostname, which is usually the intention.
### Exceptions:
If the client should expand some or all variables, this message can and should be ignored.
If the client should expand some or all variables, this message can and should be ignored.
To expand a mix of local and remote variables, the here doc end token should be unquoted, and the remote variables should be escaped, e.g.
To expand a mix of local and remote variables, the here doc end token should be unquoted, and the remote variables should be escaped, e.g.
ssh host.example.com << EOF
echo "Logged in on \$HOSTNAME from $HOSTNAME"
EOF
```sh
ssh host.example.com << EOF
echo "Logged in on \$HOSTNAME from $HOSTNAME"
EOF
```

@@ -2,18 +2,22 @@
### Problematic code:
rm "~/Desktop/$filename"
```sh
rm "~/Desktop/$filename"
```
### Correct code:
rm "$HOME/Desktop/$filename"
```sh
rm "$HOME/Desktop/$filename"
```
### Rationale:
Tilde does not expand to the user's home directory when it's single or double quoted. Use double quotes and `$HOME` instead.
Alternatively, the `~/` can be left unquoted, as in `rm ~/"Desktop/$filename"`.
Alternatively, the `~/` can be left unquoted, as in `rm ~/"Desktop/$filename"`.
### Exceptions
If you don't want the tilde to be expanded, you can ignore this message.
If you don't want the tilde to be expanded, you can ignore this message.

@@ -2,43 +2,53 @@
### Problematic code:
args='-lh "My File.txt"'
ls $args
```sh
args='-lh "My File.txt"'
ls $args
```
### Correct code:
args=(-lh "My File.txt")
ls "${args[@]}"
```sh
args=(-lh "My File.txt")
ls "${args[@]}"
```
### Rationale:
Bash does not interpret data as code. Consider almost any other languages, such as Python:
print 1+1 # prints 2
a="1+1"
print a # prints 1+1, not 2
```sh
print 1+1 # prints 2
a="1+1"
print a # prints 1+1, not 2
```
Here, `1+1` is Python syntax for adding numbers. However, passing a literal string containing this expression does not cause Python to interpret it, see the `+` and produce the calculated result.
Similarly, `"My File.txt"` is Bash syntax for a single word with a space in it. However, passing a literal string containing this expression does not cause Bash to interpret it, see the quotes and produce the tokenized result.
Similarly, `"My File.txt"` is Bash syntax for a single word with a space in it. However, passing a literal string containing this expression does not cause Bash to interpret it, see the quotes and produce the tokenized result.
The solution is to use an array instead, whenever possible.
If you due to `sh` compatibility can't use arrays, you can use `eval` instead. However, this is very insecure and easy to get wrong, leading to various forms of security vulnerabilities and breakage:
quote() { local q=${1//\'/\'\\\'\'}; echo "'$q'"; }
args="-lh $(quote "My File.txt")"
eval ls "$args" # Do not use unless you understand implications
```sh
quote() { local q=${1//\'/\'\\\'\'}; echo "'$q'"; }
args="-lh $(quote "My File.txt")"
eval ls "$args" # Do not use unless you understand implications
```
If you ever accidentally forget to use proper quotes, such as with:
for f in *.txt; do
args="-lh '$1'" # Example security exploit
eval ls "$args" # Do not copy and use
done
```sh
for f in *.txt; do
args="-lh '$1'" # Example security exploit
eval ls "$args" # Do not copy and use
done
```
Then you can use `touch "'; rm -rf \$'\x2F'; '.txt"` (or someone can trick you into downloading a file with this name, or create a zip file or git repo containing it, or changing their nick and have your chat client create the file for a chat log, or...), and running the script to list your files will run the command `rm -rf /`.
Then you can use `touch "'; rm -rf \$'\x2F'; '.txt"` (or someone can trick you into downloading a file with this name, or create a zip file or git repo containing it, or changing their nick and have your chat client create the file for a chat log, or...), and running the script to list your files will run the command `rm -rf /`.
### Exceptions
Few and far between.
Few and far between.

@@ -1,3 +1,3 @@
# Quotes/backslashes in this variable will not be respected.
See companion warning, [[SC2089]].
See companion warning, [[SC2089]].

@@ -2,21 +2,25 @@
### Problematic code:
if $(which epstopdf)
then
echo "Found epstopdf"
fi
```sh
if $(which epstopdf)
then
echo "Found epstopdf"
fi
```
### Correct code:
if which epstopdf
then
echo "Found epstopdf"
fi
```sh
if which epstopdf
then
echo "Found epstopdf"
fi
```
### Rationale:
ShellCheck has detected that you have a command that just consists of a command substitution.
ShellCheck has detected that you have a command that just consists of a command substitution.
This is typically done in order to try to get the shell to execute a command, because `$(..)` does indeed execute commands. However, it's also replaced by the output of that command.
@@ -30,4 +34,4 @@ The solution is simply to remove the surrounding `$()`. This will execute the co
### Exceptions:
If you really want to execute the output of a command rather than the command itself, you can ignore this message.
If you really want to execute the output of a command rather than the command itself, you can ignore this message.

@@ -1,3 +1,3 @@
## Remove backticks to avoid executing output.
Backticks does the same thing as `$(..)`. See [[SC2091]] for a description of the same problem with this syntax.
Backticks does the same thing as `$(..)`. See [[SC2091]] for a description of the same problem with this syntax.

@@ -2,23 +2,27 @@
### Problematic code:
grep foo file.txt | sed -e 's/foo/bar/g' > file.txt
```sh
grep foo file.txt | sed -e 's/foo/bar/g' > file.txt
```
### Correct code:
grep foo file.txt | sed -e 's/foo/bar/g' > tmpfile && mv tmpfile file.txt
```sh
grep foo file.txt | sed -e 's/foo/bar/g' > tmpfile && mv tmpfile file.txt
```
### Rationale:
Each step in a pipeline runs in parallel.
Each step in a pipeline runs in parallel.
In this case, `grep foo file.txt` will immediately try to read `file.txt` while `sed .. > file.txt` will immediately try to truncate it.
In this case, `grep foo file.txt` will immediately try to read `file.txt` while `sed .. > file.txt` will immediately try to truncate it.
This is a race condition, and results in the file being partially or (far more likely) entirely truncated.
This is a race condition, and results in the file being partially or (far more likely) entirely truncated.
### Exceptions
You can ignore this error if:
* The file is a device or named pipe. These files don't truncate in the same way.
* The command mentions the filename but doesn't read/write it, such as `echo log.txt > log.txt`.
* The command mentions the filename but doesn't read/write it, such as `echo log.txt > log.txt`.

@@ -2,19 +2,23 @@
### Problematic code:
#!/usr/bin/env bash -x
```sh
#!/usr/bin/env bash -x
```
### Correct code:
#!/usr/bin/env bash
set -x
```sh
#!/usr/bin/env bash
set -x
```
### Rationale:
Most operating systems, including Linux, FreeBSD and OS X, allow only a single parameter in the shebang. The example is equivalent to calling `env 'bash -x'` instead of `env 'bash' '-x'`, and it will therefore fail.
Most operating systems, including Linux, FreeBSD and OS X, allow only a single parameter in the shebang. The example is equivalent to calling `env 'bash -x'` instead of `env 'bash' '-x'`, and it will therefore fail.
The shebang should be rewritten to use at most one parameter. Shell options can instead be set in the body of the script.
### Exceptions
None.
None.

@@ -2,19 +2,25 @@
### Problematic code:
name=World cmd -m "Hello $name"
```sh
name=World cmd -m "Hello $name"
```
### Correct code:
export name=World
cmd -m "Hello $name"
```sh
export name=World
cmd -m "Hello $name"
```
To prevent setting the variable, this can also be done in a subshell:
(
export name=World
cmd -m "Hello $name"
) # 'name' does not leave this subshell
```sh
(
export name=World
cmd -m "Hello $name"
) # 'name' does not leave this subshell
```
### Rationale:
@@ -26,4 +32,4 @@ The solution is to set the variable and export the variable first. If limited sc
### Exceptions
In the strange and fabricated scenarios where the script and a program uses a variable name for two different purposes, you can ignore this message. This is hard to conceive, since scripts should use lowercase variable names specifically to avoid collisions with the environment.
In the strange and fabricated scenarios where the script and a program uses a variable name for two different purposes, you can ignore this message. This is hard to conceive, since scripts should use lowercase variable names specifically to avoid collisions with the environment.

@@ -1,3 +1,3 @@
## This expansion will not see the mentioned assignment.
See companion warning [[SC2097]].
See companion warning [[SC2097]].

Some files were not shown because too many files have changed in this diff Show More