mirror of
https://github.com/koalaman/shellcheck.git
synced 2025-10-03 19:29:44 +08:00
Use triple backticks rather than indenting for code.
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "$"
|
echo "$"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "\$"
|
echo "\$"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
`$` is special in double quotes, but there are some cases where it's interpreted literally:
|
`$` is special in double quotes, but there are some cases where it's interpreted literally:
|
||||||
|
@@ -2,19 +2,27 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo Yay \o/
|
echo Yay \o/
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
\git status
|
\git status
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo 'Yay \o/'
|
echo 'Yay \o/'
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
command git status
|
command git status
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,19 +2,27 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
# I want programs to show text in dutch!
|
# I want programs to show text in dutch!
|
||||||
LANGUAGE= nl
|
LANGUAGE= nl
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
# I want to run the nl command with English error messages!
|
# I want to run the nl command with English error messages!
|
||||||
LANGUAGE= nl
|
LANGUAGE= nl
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
# I want programs to show text in dutch!
|
# I want programs to show text in dutch!
|
||||||
LANGUAGE=nl
|
LANGUAGE=nl
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
# I want to run the nl command with English error messages!
|
# I want to run the nl command with English error messages!
|
||||||
LANGUAGE='' nl
|
LANGUAGE='' nl
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo “hello world”
|
echo “hello world”
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "hello world"
|
echo "hello world"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo ‘hello world’
|
echo ‘hello world’
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo 'hello world'
|
echo 'hello world'
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "Ninth parameter: $9"
|
echo "Ninth parameter: $9"
|
||||||
echo "Tenth parameter: $10"
|
echo "Tenth parameter: $10"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "Ninth parameter: $9"
|
echo "Ninth parameter: $9"
|
||||||
echo "Tenth parameter: ${10}"
|
echo "Tenth parameter: ${10}"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,17 +2,21 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
while IFS= read -r line
|
while IFS= read -r line
|
||||||
do
|
do
|
||||||
printf "%q\n" "$line"
|
printf "%q\n" "$line"
|
||||||
done <<(curl -s http://example.com)
|
done <<(curl -s http://example.com)
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
while IFS= read -r line
|
while IFS= read -r line
|
||||||
do
|
do
|
||||||
printf "%q\n" "$line"
|
printf "%q\n" "$line"
|
||||||
done < <(curl -s http://example.com)
|
done < <(curl -s http://example.com)
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -4,23 +4,29 @@
|
|||||||
|
|
||||||
Any code using `<<-` that is indented with spaces. `cat -T script` shows
|
Any code using `<<-` that is indented with spaces. `cat -T script` shows
|
||||||
|
|
||||||
|
```sh
|
||||||
cat <<- foo
|
cat <<- foo
|
||||||
Hello world
|
Hello world
|
||||||
foo
|
foo
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
Code using `<<-` must be indented with tabs. `cat -T script` shows
|
Code using `<<-` must be indented with tabs. `cat -T script` shows
|
||||||
|
|
||||||
|
```sh
|
||||||
^Icat <<- foo
|
^Icat <<- foo
|
||||||
^I^IHello world
|
^I^IHello world
|
||||||
^Ifoo
|
^Ifoo
|
||||||
|
```
|
||||||
|
|
||||||
Or simply don't indent the end token:
|
Or simply don't indent the end token:
|
||||||
|
|
||||||
|
```sh
|
||||||
cat <<- foo
|
cat <<- foo
|
||||||
Hello World
|
Hello World
|
||||||
foo
|
foo
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo &; bar
|
foo &; bar
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo & bar
|
foo & bar
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,17 +2,21 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo(input) {
|
foo(input) {
|
||||||
echo "$input"
|
echo "$input"
|
||||||
}
|
}
|
||||||
foo("hello world");
|
foo("hello world");
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo() {
|
foo() {
|
||||||
echo "$1"
|
echo "$1"
|
||||||
}
|
}
|
||||||
foo "hello world"
|
foo "hello world"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,17 +2,23 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
$greeting="Hello World"
|
$greeting="Hello World"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
greeting="Hello World"
|
greeting="Hello World"
|
||||||
|
```
|
||||||
|
|
||||||
Alternatively, if the goal was to assign to a variable whose name is in another variable (indirection), use `declare`:
|
Alternatively, if the goal was to assign to a variable whose name is in another variable (indirection), use `declare`:
|
||||||
|
|
||||||
|
```sh
|
||||||
name=foo
|
name=foo
|
||||||
declare "$name=hello world"
|
declare "$name=hello world"
|
||||||
echo "$foo"
|
echo "$foo"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo = 42
|
foo = 42
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo=42
|
foo=42
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -3,12 +3,16 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "Your username is ´whoami´"
|
echo "Your username is ´whoami´"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "Your username is $(whoami)" # Preferred
|
echo "Your username is $(whoami)" # Preferred
|
||||||
echo "Your username is `whoami`" # Deprecated, will give [SC2006]
|
echo "Your username is `whoami`" # Deprecated, will give [SC2006]
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -20,10 +24,14 @@ Backticks start command expansions, while forward ticks are literal. To help spo
|
|||||||
|
|
||||||
If you want to write out literal forward ticks, such as fancyful ascii quotation marks:
|
If you want to write out literal forward ticks, such as fancyful ascii quotation marks:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "``Proprietary software is an injustice.´´ - Richard Stallman"
|
echo "``Proprietary software is an injustice.´´ - Richard Stallman"
|
||||||
|
```
|
||||||
|
|
||||||
use single quotes instead:
|
use single quotes instead:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo '``Proprietary software is an injustice.´´ - Richard Stallman'
|
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,13 +2,17 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
greeting="hello
|
greeting="hello
|
||||||
target="world"
|
target="world"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
greeting="hello"
|
greeting="hello"
|
||||||
target="world"
|
target="world"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -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.
|
If you do want a multiline variable, just make sure the character after it is a quote, space or line feed.
|
||||||
|
|
||||||
|
```sh
|
||||||
var='multiline
|
var='multiline
|
||||||
'value
|
'value
|
||||||
|
```
|
||||||
|
|
||||||
can be rewritten for readability and to remove the warning:
|
can be rewritten for readability and to remove the warning:
|
||||||
|
|
||||||
|
```sh
|
||||||
var='multiline
|
var='multiline
|
||||||
value'
|
value'
|
||||||
|
```
|
||||||
|
|
||||||
As always `` `..` `` should be rewritten to ``$(..)``.
|
As always `` `..` `` should be rewritten to ``$(..)``.
|
@@ -2,17 +2,21 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
If true
|
If true
|
||||||
Then
|
Then
|
||||||
echo "hello"
|
echo "hello"
|
||||||
Fi
|
Fi
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
if true
|
if true
|
||||||
then
|
then
|
||||||
echo "hello"
|
echo "hello"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,18 +2,25 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
rmf() { rm -f "$@" }
|
rmf() { rm -f "$@" }
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
eval echo \${foo}
|
eval echo \${foo}
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
rmf() { rm -f "$@"; }
|
rmf() { rm -f "$@"; }
|
||||||
|
```
|
||||||
|
|
||||||
and
|
and
|
||||||
|
|
||||||
|
```sh
|
||||||
eval "echo \${foo}"
|
eval "echo \${foo}"
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -30,3 +37,4 @@ ShellCheck does not warn about `{}`, since this is frequently used with `find` a
|
|||||||
### Exceptions
|
### 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,13 +2,17 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
!#/bin/sh
|
!#/bin/sh
|
||||||
echo "Hello World"
|
echo "Hello World"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo "Hello World"
|
echo "Hello World"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,17 +2,21 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
for $var in *
|
for $var in *
|
||||||
do
|
do
|
||||||
echo "$var"
|
echo "$var"
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
for var in *
|
for var in *
|
||||||
do
|
do
|
||||||
echo "$var"
|
echo "$var"
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "$array[@]"
|
echo "$array[@]"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "${array[@]}"
|
echo "${array[@]}"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,19 +2,27 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep ^(.*)\1$ file
|
grep ^(.*)\1$ file
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
var = myfunction(value)
|
var = myfunction(value)
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep '^(.*)\1$' file
|
grep '^(.*)\1$' file
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
var=$(myfunction value)
|
var=$(myfunction value)
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,18 +2,22 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
if true
|
if true
|
||||||
then
|
then
|
||||||
echo hello
|
echo hello
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
if true
|
if true
|
||||||
then
|
then
|
||||||
echo hello
|
echo hello
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -21,11 +25,13 @@ 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:
|
In some cases, it can even be caused by bad quoting:
|
||||||
|
|
||||||
|
```sh
|
||||||
var="foo
|
var="foo
|
||||||
if [[ $var = "bar ]
|
if [[ $var = "bar ]
|
||||||
then
|
then
|
||||||
echo true
|
echo true
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
In this case, the `if` ends up inside the double quotes, leaving the `then` dangling.
|
In this case, the `if` ends up inside the double quotes, leaving the `then` dangling.
|
||||||
|
|
||||||
|
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
. "$(find_install_dir)/lib.sh"
|
. "$(find_install_dir)/lib.sh"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
# shellcheck source=src/lib.sh
|
# shellcheck source=src/lib.sh
|
||||||
. "$(find_install_dir)/lib.sh"
|
. "$(find_install_dir)/lib.sh"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -4,12 +4,16 @@ Reasons include: file not found, no permissions, not included on the command lin
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
source somefile
|
source somefile
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
source somefile
|
source somefile
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
source mylib
|
source mylib
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
# shellcheck disable=SC1094
|
# shellcheck disable=SC1094
|
||||||
source mylib
|
source mylib
|
||||||
|
```
|
||||||
|
|
||||||
(or fix `mylib`)
|
(or fix `mylib`)
|
||||||
|
|
||||||
|
@@ -2,17 +2,23 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
var==value
|
var==value
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
Assignment:
|
Assignment:
|
||||||
|
|
||||||
|
```sh
|
||||||
var=value
|
var=value
|
||||||
|
```
|
||||||
|
|
||||||
Comparison:
|
Comparison:
|
||||||
|
|
||||||
|
```sh
|
||||||
[ "$var" = value ]
|
[ "$var" = value ]
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### 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:
|
If you wanted to assign a literal equals sign, use quotes to make this clear:
|
||||||
|
|
||||||
|
```sh
|
||||||
var="=sum(A1:A10)"
|
var="=sum(A1:A10)"
|
||||||
|
```
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
eval $var=(a b)
|
eval $var=(a b)
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
eval "$var=(a b)"
|
eval "$var=(a b)"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,17 +2,21 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
while sleep 1
|
while sleep 1
|
||||||
do# show time
|
do# show time
|
||||||
date
|
date
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
while sleep 1
|
while sleep 1
|
||||||
do # show time
|
do # show time
|
||||||
date
|
date
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
string="stirng" ; echo "$string" | sed -e "s/ir/ri/"
|
string="stirng" ; echo "$string" | sed -e "s/ir/ri/"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
string="stirng" ; echo "${string//ir/ri}"
|
string="stirng" ; echo "${string//ir/ri}"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### 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.
|
Occasionally a more complex sed substitution is required. For example, getting the last character of a string.
|
||||||
|
|
||||||
|
```sh
|
||||||
string="stirng" ; echo "$string" | sed -e "s/^.*\(.\)$/\1/"
|
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.
|
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.
|
||||||
|
@@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
i=$(expr 1 + 2)
|
i=$(expr 1 + 2)
|
||||||
l=$(expr length "$var")
|
l=$(expr length "$var")
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
i=$((1+2))
|
i=$((1+2))
|
||||||
l=${#var}
|
l=${#var}
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,20 +2,26 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo $(($n+1))
|
echo $(($n+1))
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo $((n+1))
|
echo $((n+1))
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### 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:
|
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:
|
||||||
|
|
||||||
|
```sh
|
||||||
$ a='1+1'
|
$ a='1+1'
|
||||||
$ echo $(($a * 5)) # becomes 1+1*5
|
$ echo $(($a * 5)) # becomes 1+1*5
|
||||||
6
|
6
|
||||||
$ echo $((a * 5)) # evaluates as (1+1)*5
|
$ echo $((a * 5)) # evaluates as (1+1)*5
|
||||||
10
|
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.
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code
|
### Problematic code
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "Current time: `date`"
|
echo "Current time: `date`"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code
|
### Correct code
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "Current time: $(date)"
|
echo "Current time: $(date)"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale
|
### Rationale
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . | echo
|
find . | echo
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
find .
|
find .
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic Code:
|
### Problematic Code:
|
||||||
|
|
||||||
|
```sh
|
||||||
ps ax | grep -v grep | grep "$service" > /dev/null
|
ps ax | grep -v grep | grep "$service" > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
### Correct Code:
|
### Correct Code:
|
||||||
|
|
||||||
|
```sh
|
||||||
pgrep -f "$service" > /dev/null
|
pgrep -f "$service" > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### 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?
|
What if you have the pid and you are looking for the matching program name?
|
||||||
|
|
||||||
|
```sh
|
||||||
pid=123; ps ax | grep "$pid"
|
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?
|
What if you want a range of the ps field, like from the 16th space to the end of the line?
|
||||||
|
|
||||||
|
```sh
|
||||||
ps ax | grep "$pid" | cut -d" " -f16-
|
ps ax | grep "$pid" | cut -d" " -f16-
|
||||||
|
```
|
||||||
|
|
||||||
Both are valid cases where SC2009 is not valid.
|
Both are valid cases where SC2009 is not valid.
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
ls -l | grep " $USER " | grep '\.txt$'
|
ls -l | grep " $USER " | grep '\.txt$'
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -maxdepth 1 -name '*.txt' -user "$USER"
|
find . -maxdepth 1 -name '*.txt' -user "$USER"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -14,11 +18,13 @@
|
|||||||
|
|
||||||
Here's an example:
|
Here's an example:
|
||||||
|
|
||||||
|
```sh
|
||||||
$ ls -l
|
$ ls -l
|
||||||
total 0
|
total 0
|
||||||
-rw-r----- 1 me me 0 Feb 5 20:11 foo?bar
|
-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 2011 foo?bar
|
||||||
-rw-r----- 1 me me 0 Feb 5 20:11 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.
|
||||||
|
|
||||||
|
12
SC2013.md
12
SC2013.md
@@ -2,24 +2,30 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
for line in $(cat file | grep -v '^ *#')
|
for line in $(cat file | grep -v '^ *#')
|
||||||
do
|
do
|
||||||
echo "Line: $line"
|
echo "Line: $line"
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep -v '^ *#' < file | while IFS= read -r line
|
grep -v '^ *#' < file | while IFS= read -r line
|
||||||
do
|
do
|
||||||
echo "Line: $line"
|
echo "Line: $line"
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
or without a subshell (bash, zsh, ksh):
|
or without a subshell (bash, zsh, ksh):
|
||||||
|
|
||||||
|
```sh
|
||||||
while IFS= read -r line
|
while IFS= read -r line
|
||||||
do
|
do
|
||||||
echo "Line: $line"
|
echo "Line: $line"
|
||||||
done < <(grep -v '^ *#' < file)
|
done < <(grep -v '^ *#' < file)
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -27,20 +33,26 @@ For loops by default (subject to `$IFS`) read word by word. Additionally, glob e
|
|||||||
|
|
||||||
Given this text file:
|
Given this text file:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo *
|
foo *
|
||||||
bar
|
bar
|
||||||
|
```
|
||||||
|
|
||||||
The for loop will print:
|
The for loop will print:
|
||||||
|
|
||||||
|
```sh
|
||||||
Line: foo
|
Line: foo
|
||||||
Line: aardwark.jpg
|
Line: aardwark.jpg
|
||||||
Line: bullfrog.jpg
|
Line: bullfrog.jpg
|
||||||
...
|
...
|
||||||
|
```
|
||||||
|
|
||||||
The while loop will print:
|
The while loop will print:
|
||||||
|
|
||||||
|
```sh
|
||||||
Line: foo *
|
Line: foo *
|
||||||
Line: bar
|
Line: bar
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Exceptions
|
### Exceptions
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -name '*.tar' -exec tar xf {} -C "$(dirname {})" \;
|
find . -name '*.tar' -exec tar xf {} -C "$(dirname {})" \;
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -name '*.tar' -exec sh -c 'tar xf "$1" -C "$(dirname "$1")"' _ {} \;
|
find . -name '*.tar' -exec sh -c 'tar xf "$1" -C "$(dirname "$1")"' _ {} \;
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -14,8 +18,10 @@ Bash evaluates any command substitutions before the command they feature in is e
|
|||||||
|
|
||||||
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:
|
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
|
||||||
$ sh -c 'echo "$1 is in $(dirname "$1")"' _ "mydir/myfile"
|
$ sh -c 'echo "$1 is in $(dirname "$1")"' _ "mydir/myfile"
|
||||||
mydir/myfile is in mydir
|
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 .
|
||||||
|
|
||||||
|
@@ -2,16 +2,20 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ $dryrun ]] && echo "Would delete file" || rm file
|
[[ $dryrun ]] && echo "Would delete file" || rm file
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
if [[ $dryrun ]]
|
if [[ $dryrun ]]
|
||||||
then
|
then
|
||||||
echo "Would delete file"
|
echo "Would delete file"
|
||||||
else
|
else
|
||||||
rm file
|
rm file
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
name=World
|
name=World
|
||||||
echo 'Hello $name'
|
echo 'Hello $name'
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
name=World
|
name=World
|
||||||
echo "Hello $name"
|
echo "Hello $name"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -18,7 +22,9 @@ 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:
|
Note that if you have other items that needs single quoting, you can use both in a single word:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo '$1 USD is '"$rate GBP"
|
echo '$1 USD is '"$rate GBP"
|
||||||
|
```
|
||||||
|
|
||||||
### Exceptions
|
### Exceptions
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
percent=$((count/total*100))
|
percent=$((count/total*100))
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
percent=$((count*100/total))
|
percent=$((count*100/total))
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo 'hello world' | tr 'hello' 'goodbye'
|
echo 'hello world' | tr 'hello' 'goodbye'
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo 'hello world' | sed -e 's/hello/goodbye/g'
|
echo 'hello world' | sed -e 's/hello/goodbye/g'
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
# Note that unlike globs, o* here matches 'ooo' but not 'oscar'
|
# Note that unlike globs, o* here matches 'ooo' but not 'oscar'
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep 'foo*'
|
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:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep '^foo'
|
grep '^foo'
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
PS1='\e[36m\$ \e(B\e[m'
|
PS1='\e[36m\$ \e(B\e[m'
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
PS1='\[\e[36m\]\$ \[\e(B\e[m\]'
|
PS1='\[\e[36m\]\$ \[\e(B\e[m\]'
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
10
SC2026.md
10
SC2026.md
@@ -2,33 +2,43 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
alias server_uptime='ssh $host 'uptime -p''
|
alias server_uptime='ssh $host 'uptime -p''
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
alias server_uptime='ssh $host '"'uptime -p'"
|
alias server_uptime='ssh $host '"'uptime -p'"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### 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:
|
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:
|
||||||
|
|
||||||
|
```sh
|
||||||
# v--------match--------v
|
# v--------match--------v
|
||||||
alias server_uptime='ssh $host 'uptime -p''
|
alias server_uptime='ssh $host 'uptime -p''
|
||||||
# ^--match--^
|
# ^--match--^
|
||||||
|
```
|
||||||
|
|
||||||
The shell, meanwhile, always terminates single quoted strings at the first possible single quote:
|
The shell, meanwhile, always terminates single quoted strings at the first possible single quote:
|
||||||
|
|
||||||
|
```sh
|
||||||
# v---match--v
|
# v---match--v
|
||||||
alias server_uptime='ssh $host 'uptime -p''
|
alias server_uptime='ssh $host 'uptime -p''
|
||||||
# ^^
|
# ^^
|
||||||
|
```
|
||||||
|
|
||||||
Which is the same thing as `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:
|
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:
|
||||||
|
|
||||||
|
```sh
|
||||||
# v--match---v
|
# v--match---v
|
||||||
alias server_uptime='ssh $host '"'uptime -p'"
|
alias server_uptime='ssh $host '"'uptime -p'"
|
||||||
# ^---match---^
|
# ^---match---^
|
||||||
|
```
|
||||||
|
|
||||||
This results in an alias with embedded single quotes.
|
This results in an alias with embedded single quotes.
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "You enter "$HOSTNAME". You can smell the wumpus." >> /etc/issue
|
echo "You enter "$HOSTNAME". You can smell the wumpus." >> /etc/issue
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "You enter $HOSTNAME. You can smell the wumpus." >> /etc/issue
|
echo "You enter $HOSTNAME. You can smell the wumpus." >> /etc/issue
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -14,9 +18,11 @@ 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:
|
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:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "You enter "$HOSTNAME". You can smell the wumpus."
|
echo "You enter "$HOSTNAME". You can smell the wumpus."
|
||||||
|----------| |---------------------------|
|
|----------| |---------------------------|
|
||||||
Quoted No quotes Quoted
|
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.
|
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.
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "Name:\t$value"
|
echo "Name:\t$value"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
printf "Name:\t%s\n" "$value"
|
printf "Name:\t%s\n" "$value"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -18,7 +22,9 @@ Other, non-portable methods include `echo -e '\t'` and `echo $'\t'`. ShellCheck
|
|||||||
|
|
||||||
If you actually wanted a literal backslash-t, use
|
If you actually wanted a literal backslash-t, use
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "\\t"
|
echo "\\t"
|
||||||
|
```
|
||||||
|
|
||||||
### Exceptions
|
### Exceptions
|
||||||
|
|
||||||
|
@@ -2,21 +2,29 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
ssh host "echo $HOSTNAME"
|
ssh host "echo $HOSTNAME"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
ssh host "echo \$HOSTNAME"
|
ssh host "echo \$HOSTNAME"
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
ssh host 'echo $HOSTNAME'
|
ssh host 'echo $HOSTNAME'
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
Bash expands all arguments that are not escaped/singlequoted. This means that the problematic code is identical to
|
Bash expands all arguments that are not escaped/singlequoted. This means that the problematic code is identical to
|
||||||
|
|
||||||
|
```sh
|
||||||
ssh host "echo clienthostname"
|
ssh host "echo clienthostname"
|
||||||
|
```
|
||||||
|
|
||||||
and will print out the client's hostname, not the server's hostname.
|
and will print out the client's hostname, not the server's hostname.
|
||||||
|
|
||||||
|
12
SC2031.md
12
SC2031.md
@@ -29,33 +29,45 @@ Here are some constructs that cause subshells (shellcheck may not warn about all
|
|||||||
|
|
||||||
Pipelines:
|
Pipelines:
|
||||||
|
|
||||||
|
```sh
|
||||||
subshell1 | subshell2 | subshell3 # Bash, Dash, Ash
|
subshell1 | subshell2 | subshell3 # Bash, Dash, Ash
|
||||||
subshell1 | subshell2 | regular # Ksh, Zsh
|
subshell1 | subshell2 | regular # Ksh, Zsh
|
||||||
|
```
|
||||||
|
|
||||||
Command substitution:
|
Command substitution:
|
||||||
|
|
||||||
|
```sh
|
||||||
regular "$(subshell1)" "`subshell2`"
|
regular "$(subshell1)" "`subshell2`"
|
||||||
|
```
|
||||||
|
|
||||||
Process substitution:
|
Process substitution:
|
||||||
|
|
||||||
|
```sh
|
||||||
regular <(subshell1) >(subshell2)
|
regular <(subshell1) >(subshell2)
|
||||||
|
```
|
||||||
|
|
||||||
Some forms of grouping:
|
Some forms of grouping:
|
||||||
|
|
||||||
|
```sh
|
||||||
( subshell )
|
( subshell )
|
||||||
{ regular; }
|
{ regular; }
|
||||||
|
```
|
||||||
|
|
||||||
Backgrounding:
|
Backgrounding:
|
||||||
|
|
||||||
|
```sh
|
||||||
subshell1 &
|
subshell1 &
|
||||||
subshell2 &
|
subshell2 &
|
||||||
|
```
|
||||||
|
|
||||||
Anything executed by external processes:
|
Anything executed by external processes:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -exec subshell1 {} \;
|
find . -exec subshell1 {} \;
|
||||||
find . -print0 | xargs -0 subshell2
|
find . -print0 | xargs -0 subshell2
|
||||||
sudo subshell3
|
sudo subshell3
|
||||||
su -c subshell4
|
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.
|
||||||
|
|
||||||
|
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo() { bar --baz "$@"; frob --baz "$@"; };
|
foo() { bar --baz "$@"; frob --baz "$@"; };
|
||||||
find . -exec foo {} +
|
find . -exec foo {} +
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -exec sh -c 'bar --baz "$@"; frob --baz "$@";' -- {} +
|
find . -exec sh -c 'bar --baz "$@"; frob --baz "$@";' -- {} +
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -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.
|
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.
|
||||||
|
|
||||||
|
```sh
|
||||||
nobody() {
|
nobody() {
|
||||||
sudo -u "nobody" "$@"
|
sudo -u "nobody" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
10
SC2034.md
10
SC2034.md
@@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo=42
|
foo=42
|
||||||
echo "$FOO"
|
echo "$FOO"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo=42
|
foo=42
|
||||||
echo "$foo"
|
echo "$foo"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -22,20 +26,26 @@ ShellCheck may not always realize that the variable is in use (especially with i
|
|||||||
|
|
||||||
For throwaway variables, consider using `_` as a dummy:
|
For throwaway variables, consider using `_` as a dummy:
|
||||||
|
|
||||||
|
```sh
|
||||||
read _ last _ zip _ _ <<< "$str"
|
read _ last _ zip _ _ <<< "$str"
|
||||||
echo "$last, $zip"
|
echo "$last, $zip"
|
||||||
|
```
|
||||||
|
|
||||||
or use a directive to disable the warning:
|
or use a directive to disable the warning:
|
||||||
|
|
||||||
|
```sh
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
read first last email zip lat lng <<< "$str"
|
read first last email zip lat lng <<< "$str"
|
||||||
echo "$last, $zip"
|
echo "$last, $zip"
|
||||||
|
```
|
||||||
|
|
||||||
For indirection, there's not much you can do without rewriting to use arrays or similar:
|
For indirection, there's not much you can do without rewriting to use arrays or similar:
|
||||||
|
|
||||||
|
```sh
|
||||||
bar=42 # will always appear unused
|
bar=42 # will always appear unused
|
||||||
foo=bar
|
foo=bar
|
||||||
echo "${!foo}"
|
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.
|
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.
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
sum=find | wc -l
|
sum=find | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
sum=$(find | wc -l)
|
sum=$(find | wc -l)
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -type f | xargs md5sum
|
find . -type f | xargs md5sum
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -type f -print0 | xargs -0 md5sum
|
find . -type f -print0 | xargs -0 md5sum
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,17 +2,21 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
for i in 'seq 1 10'
|
for i in 'seq 1 10'
|
||||||
do
|
do
|
||||||
echo "$i"
|
echo "$i"
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
for i in $(seq 1 10)
|
for i in $(seq 1 10)
|
||||||
do
|
do
|
||||||
echo "$i"
|
echo "$i"
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
10
SC2043.md
10
SC2043.md
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
for var in value
|
for var in value
|
||||||
do
|
do
|
||||||
echo "$var"
|
echo "$var"
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
@@ -13,19 +15,27 @@ Correct code depends on what you want to do.
|
|||||||
|
|
||||||
To iterate over files in a directory, instead of `for var in /my/dir` use:
|
To iterate over files in a directory, instead of `for var in /my/dir` use:
|
||||||
|
|
||||||
|
```sh
|
||||||
for var in /my/dir/* ; do echo "$var"; done
|
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:
|
To iterate over lines in a file or command output, use a while read loop instead:
|
||||||
|
|
||||||
|
```sh
|
||||||
mycommand | while IFS= read -r line; do echo "$line"; done
|
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
|
To iterate over *words* written to a command or function's stdout, instead of `for var in myfunction`, use
|
||||||
|
|
||||||
|
```sh
|
||||||
for var in $(myfunction); do echo "$var"; done
|
for var in $(myfunction); do echo "$var"; done
|
||||||
|
```
|
||||||
|
|
||||||
To iterate over *words* in a variable, instead of `for var in myvariable`, use
|
To iterate over *words* in a variable, instead of `for var in myvariable`, use
|
||||||
|
|
||||||
|
```sh
|
||||||
for var in $myvariable; do echo "$var"; done
|
for var in $myvariable; do echo "$var"; done
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
12
SC2044.md
12
SC2044.md
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
for file in $(find mydir -mtime -7 -name '*.mp3')
|
for file in $(find mydir -mtime -7 -name '*.mp3')
|
||||||
do
|
do
|
||||||
let count++
|
let count++
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
play "$file"
|
play "$file"
|
||||||
done
|
done
|
||||||
echo "Played $count files"
|
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).
|
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).
|
||||||
|
|
||||||
@@ -18,6 +20,7 @@ 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:
|
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:
|
||||||
|
|
||||||
|
```sh
|
||||||
while IFS= read -r -d '' file
|
while IFS= read -r -d '' file
|
||||||
do
|
do
|
||||||
let count++
|
let count++
|
||||||
@@ -25,6 +28,7 @@ The most general fix (that requires the least amount of thinking to apply) is ha
|
|||||||
play "$file"
|
play "$file"
|
||||||
done < <(find mydir -mtime -7 -name '*.mp3' -print0)
|
done < <(find mydir -mtime -7 -name '*.mp3' -print0)
|
||||||
echo "Played $count files"
|
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.
|
||||||
|
|
||||||
@@ -34,6 +38,7 @@ 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:
|
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:
|
||||||
|
|
||||||
|
```sh
|
||||||
shopt -s globstar nullglob
|
shopt -s globstar nullglob
|
||||||
for file in mydir/**/*.mp3
|
for file in mydir/**/*.mp3
|
||||||
do
|
do
|
||||||
@@ -42,6 +47,7 @@ If you don't need `find` logic like `-mtime -7` and just use it to match globs r
|
|||||||
play "$file"
|
play "$file"
|
||||||
done
|
done
|
||||||
echo "Played $count files"
|
echo "Played $count files"
|
||||||
|
```
|
||||||
|
|
||||||
This is bash specific.
|
This is bash specific.
|
||||||
|
|
||||||
@@ -50,6 +56,7 @@ This is bash specific.
|
|||||||
|
|
||||||
If you need POSIX compliance, this is a fair approach:
|
If you need POSIX compliance, this is a fair approach:
|
||||||
|
|
||||||
|
```sh
|
||||||
find mydir ! -name "$(printf "*\n*")" -name '*.mp3' > tmp
|
find mydir ! -name "$(printf "*\n*")" -name '*.mp3' > tmp
|
||||||
while IFS= read -r file
|
while IFS= read -r file
|
||||||
do
|
do
|
||||||
@@ -59,6 +66,7 @@ If you need POSIX compliance, this is a fair approach:
|
|||||||
done < tmp
|
done < tmp
|
||||||
rm tmp
|
rm tmp
|
||||||
echo "Played $count files"
|
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.
|
||||||
|
|
||||||
@@ -68,8 +76,10 @@ If you don't need variables to be available after the loop (here, if you don't n
|
|||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
|
```sh
|
||||||
# Simple and POSIX
|
# Simple and POSIX
|
||||||
find mydir -name '*.mp3' -exec play {} \;
|
find mydir -name '*.mp3' -exec play {} \;
|
||||||
|
```
|
||||||
|
|
||||||
This does not allow things like `let counter++` because `let` is a shell builtin, not an external command.
|
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):
|
If we do need a shell script body but no aggregation, you can do the above but invoking `sh` (this is still POSIX):
|
||||||
|
|
||||||
|
```sh
|
||||||
find mydir -name '*.mp3' -exec sh -c '
|
find mydir -name '*.mp3' -exec sh -c '
|
||||||
echo "Playing ${1%.mp3}"
|
echo "Playing ${1%.mp3}"
|
||||||
play "$1"
|
play "$1"
|
||||||
' sh {} \;
|
' 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.
|
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.
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
cp $* ~/dir
|
cp $* ~/dir
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
cp "$@" ~/dir
|
cp "$@" ~/dir
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,17 +2,21 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
if [[ $1 != foo || $1 != bar ]]
|
if [[ $1 != foo || $1 != bar ]]
|
||||||
then
|
then
|
||||||
echo "$1 is not foo or bar"
|
echo "$1 is not foo or bar"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
if [[ $1 != foo && $1 != bar ]]
|
if [[ $1 != foo && $1 != bar ]]
|
||||||
then
|
then
|
||||||
echo "$1 is not foo or bar"
|
echo "$1 is not foo or bar"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,19 +2,25 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
printf "Hello, $NAME\n"
|
printf "Hello, $NAME\n"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
printf "Hello, %s\n" "$NAME"
|
printf "Hello, %s\n" "$NAME"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### 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:
|
`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:
|
||||||
|
|
||||||
|
```sh
|
||||||
coverage='96%'
|
coverage='96%'
|
||||||
printf "Unit test coverage: %s\n" "$coverage"
|
printf "Unit test coverage: %s\n" "$coverage"
|
||||||
printf "Unit test coverage: $coverage\n"
|
printf "Unit test coverage: $coverage\n"
|
||||||
|
```
|
||||||
|
|
||||||
The first printf writes `Unit test coverage: 96%`.
|
The first printf writes `Unit test coverage: 96%`.
|
||||||
|
|
||||||
@@ -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:
|
Sometimes you may actually want to interpret data as a format string, like in:
|
||||||
|
|
||||||
|
```sh
|
||||||
hexToAscii() { printf "\x$1"; }
|
hexToAscii() { printf "\x$1"; }
|
||||||
hexToAscii 21
|
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:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
tr -cd [:digit:]
|
tr -cd [:digit:]
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
tr -cd '[:digit:]'
|
tr -cd '[:digit:]'
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep '*foo*'
|
grep '*foo*'
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep 'foo' # or more explicitly, grep '.*foo.*'
|
grep 'foo' # or more explicitly, grep '.*foo.*'
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
trap "echo \"Finished on $(date)\"" EXIT
|
trap "echo \"Finished on $(date)\"" EXIT
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
trap 'echo "Finished on $(date)"' EXIT
|
trap 'echo "Finished on $(date)"' EXIT
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
10
SC2066.md
10
SC2066.md
@@ -2,30 +2,40 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
for s in "$(mycommand)"; do echo "$s"; done
|
for s in "$(mycommand)"; do echo "$s"; done
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### 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):
|
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):
|
||||||
|
|
||||||
|
```sh
|
||||||
hello world
|
hello world
|
||||||
My *.png
|
My *.png
|
||||||
|
```
|
||||||
|
|
||||||
#### Loop over each line without globbing (`hello world`, `My *.png`)
|
#### Loop over each line without globbing (`hello world`, `My *.png`)
|
||||||
|
|
||||||
|
```sh
|
||||||
mycommand | while IFS= read -r s; do echo "$s"; done
|
mycommand | while IFS= read -r s; do echo "$s"; done
|
||||||
|
```
|
||||||
|
|
||||||
#### Loop over each word with globbing (`hello`, `world`, `My`, `file.png`, `My cat.png`):
|
#### Loop over each word with globbing (`hello`, `world`, `My`, `file.png`, `My cat.png`):
|
||||||
|
|
||||||
|
```sh
|
||||||
# relies on the fact that IFS by default contains space-tab-linefeed
|
# relies on the fact that IFS by default contains space-tab-linefeed
|
||||||
for s in $(mycommand); do echo "$s"; done
|
for s in $(mycommand); do echo "$s"; done
|
||||||
|
```
|
||||||
|
|
||||||
#### Loop over each line with globbing (`hello world`, `My cat.png`)
|
#### Loop over each line with globbing (`hello world`, `My cat.png`)
|
||||||
|
|
||||||
|
```sh
|
||||||
# explicitly set IFS to contain only a line feed
|
# explicitly set IFS to contain only a line feed
|
||||||
IFS='
|
IFS='
|
||||||
'
|
'
|
||||||
for s in $(mycommand); do echo "$s"; done
|
for s in $(mycommand); do echo "$s"; done
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -type f -exec shellcheck {} | wc -l \;
|
find . -type f -exec shellcheck {} | wc -l \;
|
||||||
find . -exec echo {} ;
|
find . -exec echo {} ;
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -type f -exec sh -c 'shellcheck "$1" | wc -l' -- {} \;
|
find . -type f -exec sh -c 'shellcheck "$1" | wc -l' -- {} \;
|
||||||
find . -exec echo {} \;
|
find . -exec echo {} \;
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -18,11 +22,15 @@
|
|||||||
|
|
||||||
To instead go through each file and run `foo file && bar file` on it, invoke a shell that can interpret `&&`:
|
To instead go through each file and run `foo file && bar file` on it, invoke a shell that can interpret `&&`:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -exec sh 'foo "$1" && bar "$1"' -- {} \;
|
find . -exec sh 'foo "$1" && bar "$1"' -- {} \;
|
||||||
|
```
|
||||||
|
|
||||||
You can also use find `-a` instead of shell `&&`:
|
You can also use find `-a` instead of shell `&&`:
|
||||||
|
|
||||||
|
```sh
|
||||||
find . -exec foo {} \; -a -exec bar {} \;
|
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).
|
This will have the same effect (`-a` is also the default when two commands are specified, and can therefore be omitted).
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
cp $@ ~/dir
|
cp $@ ~/dir
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
cp "$@" ~/dir
|
cp "$@" ~/dir
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
firefox 2>&1 > /dev/null
|
firefox 2>&1 > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
firefox > /dev/null 2>&1
|
firefox > /dev/null 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,40 +2,48 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
if [ -n $var ]
|
if [ -n $var ]
|
||||||
then
|
then
|
||||||
echo "var has a value"
|
echo "var has a value"
|
||||||
else
|
else
|
||||||
echo "var is empty"
|
echo "var is empty"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
In bash/ksh:
|
In bash/ksh:
|
||||||
|
|
||||||
|
```sh
|
||||||
if [[ -n $var ]]
|
if [[ -n $var ]]
|
||||||
then
|
then
|
||||||
echo "var has a value"
|
echo "var has a value"
|
||||||
else
|
else
|
||||||
echo "var is empty"
|
echo "var is empty"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
In POSIX:
|
In POSIX:
|
||||||
|
|
||||||
|
```sh
|
||||||
if [ -n "$var" ]
|
if [ -n "$var" ]
|
||||||
then
|
then
|
||||||
echo "var has a value"
|
echo "var has a value"
|
||||||
else
|
else
|
||||||
echo "var is empty"
|
echo "var is empty"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
When `$var` is unquoted, a blank value will cause it to wordsplit and disappear. If `$var` is empty, these two statements are identical:
|
When `$var` is unquoted, a blank value will cause it to wordsplit and disappear. If `$var` is empty, these two statements are identical:
|
||||||
|
|
||||||
|
```sh
|
||||||
[ -n $var ]
|
[ -n $var ]
|
||||||
[ -n ]
|
[ -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 ]`.
|
`[ 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 ]`.
|
||||||
|
|
||||||
|
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ 2 -lt 3.14 ]]
|
[[ 2 -lt 3.14 ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ 200 -lt 314 ]] # Use fixed point math
|
[[ 200 -lt 314 ]] # Use fixed point math
|
||||||
[[ $(echo "2 < 3.14" | bc) == 1 ]] # Use bc
|
[[ $(echo "2 < 3.14" | bc) == 1 ]] # Use bc
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ $foo =~ "^fo+bar$" ]]
|
[[ $foo =~ "^fo+bar$" ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ $foo =~ ^fo+bar$ ]]
|
[[ $foo =~ ^fo+bar$ ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ 0=1 ]]
|
[[ 0=1 ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ 0 = 1 ]]
|
[[ 0 = 1 ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,35 +2,43 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
var_1="hello world"
|
var_1="hello world"
|
||||||
n=1
|
n=1
|
||||||
echo "${var_$n}"
|
echo "${var_$n}"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
Bash/ksh:
|
Bash/ksh:
|
||||||
|
|
||||||
|
```sh
|
||||||
# Use arrays instead of dynamic names
|
# Use arrays instead of dynamic names
|
||||||
declare -a var
|
declare -a var
|
||||||
var[1]="hello world"
|
var[1]="hello world"
|
||||||
n=1
|
n=1
|
||||||
echo "${var[n]}"
|
echo "${var[n]}"
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
# Expand variable names dynamically
|
# Expand variable names dynamically
|
||||||
var_1="hello world"
|
var_1="hello world"
|
||||||
n=1
|
n=1
|
||||||
name="var_$n"
|
name="var_$n"
|
||||||
echo "${!name}"
|
echo "${!name}"
|
||||||
|
```
|
||||||
|
|
||||||
POSIX sh:
|
POSIX sh:
|
||||||
|
|
||||||
|
```sh
|
||||||
# Expand dynamically with eval
|
# Expand dynamically with eval
|
||||||
var_1="hello world"
|
var_1="hello world"
|
||||||
n=1
|
n=1
|
||||||
eval "tmp=\$var_$n"
|
eval "tmp=\$var_$n"
|
||||||
echo "${tmp}"
|
echo "${tmp}"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
12
SC2084.md
12
SC2084.md
@@ -2,41 +2,53 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
i=4
|
i=4
|
||||||
$(( i++ ))
|
$(( i++ ))
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
Bash, Ksh:
|
Bash, Ksh:
|
||||||
|
|
||||||
|
```sh
|
||||||
i=4
|
i=4
|
||||||
(( i++ ))
|
(( i++ ))
|
||||||
|
```
|
||||||
|
|
||||||
POSIX (assuming `++` is supported):
|
POSIX (assuming `++` is supported):
|
||||||
|
|
||||||
|
```sh
|
||||||
i=4
|
i=4
|
||||||
_=$(( i++ ))
|
_=$(( i++ ))
|
||||||
|
```
|
||||||
|
|
||||||
Alternative POSIX version that does not preserve the exit code:
|
Alternative POSIX version that does not preserve the exit code:
|
||||||
|
|
||||||
|
```sh
|
||||||
: $(( i++ ))
|
: $(( i++ ))
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### 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:
|
`$((..))` 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:
|
||||||
|
|
||||||
|
```sh
|
||||||
$ i=4
|
$ i=4
|
||||||
$ $(( i++ ))
|
$ $(( i++ ))
|
||||||
4: command not found
|
4: command not found
|
||||||
$ echo $i
|
$ echo $i
|
||||||
5
|
5
|
||||||
|
```
|
||||||
|
|
||||||
To avoid trying to execute the number as a command name, use one of the methods mentioned:
|
To avoid trying to execute the number as a command name, use one of the methods mentioned:
|
||||||
|
|
||||||
|
```sh
|
||||||
$ i=4
|
$ i=4
|
||||||
$ _=$(( i++ ))
|
$ _=$(( i++ ))
|
||||||
$ echo $i
|
$ echo $i
|
||||||
5
|
5
|
||||||
|
```
|
||||||
|
|
||||||
### Exceptions:
|
### Exceptions:
|
||||||
|
|
||||||
|
@@ -2,15 +2,18 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
ssh host.example.com << EOF
|
ssh host.example.com << EOF
|
||||||
echo "Logged in on $HOSTNAME"
|
echo "Logged in on $HOSTNAME"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
ssh host.example.com << "EOF"
|
ssh host.example.com << "EOF"
|
||||||
echo "Logged in on $HOSTNAME"
|
echo "Logged in on $HOSTNAME"
|
||||||
EOF
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -20,15 +23,19 @@ This means that before sending the commands to the server, the client replaces `
|
|||||||
|
|
||||||
Scripts with any kind of variable use are especially problematic because all references will be expanded before the script run. For example,
|
Scripts with any kind of variable use are especially problematic because all references will be expanded before the script run. For example,
|
||||||
|
|
||||||
|
```sh
|
||||||
ssh host << EOF
|
ssh host << EOF
|
||||||
x="$(uname -a)"
|
x="$(uname -a)"
|
||||||
echo "$x"
|
echo "$x"
|
||||||
EOF
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
will never print anything, neither client nor server details, since before evaluation, it will be expanded to:
|
will never print anything, neither client nor server details, since before evaluation, it will be expanded to:
|
||||||
|
|
||||||
|
```sh
|
||||||
x="Linux localhost ... x86_64 GNU/Linux"
|
x="Linux localhost ... x86_64 GNU/Linux"
|
||||||
echo ""
|
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.
|
||||||
|
|
||||||
@@ -38,6 +45,8 @@ If the client should expand some or all variables, this message can and should b
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
```sh
|
||||||
ssh host.example.com << EOF
|
ssh host.example.com << EOF
|
||||||
echo "Logged in on \$HOSTNAME from $HOSTNAME"
|
echo "Logged in on \$HOSTNAME from $HOSTNAME"
|
||||||
EOF
|
EOF
|
||||||
|
```
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
rm "~/Desktop/$filename"
|
rm "~/Desktop/$filename"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
rm "$HOME/Desktop/$filename"
|
rm "$HOME/Desktop/$filename"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
10
SC2089.md
10
SC2089.md
@@ -2,21 +2,27 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
args='-lh "My File.txt"'
|
args='-lh "My File.txt"'
|
||||||
ls $args
|
ls $args
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
args=(-lh "My File.txt")
|
args=(-lh "My File.txt")
|
||||||
ls "${args[@]}"
|
ls "${args[@]}"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
Bash does not interpret data as code. Consider almost any other languages, such as Python:
|
Bash does not interpret data as code. Consider almost any other languages, such as Python:
|
||||||
|
|
||||||
|
```sh
|
||||||
print 1+1 # prints 2
|
print 1+1 # prints 2
|
||||||
a="1+1"
|
a="1+1"
|
||||||
print a # prints 1+1, not 2
|
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.
|
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.
|
||||||
|
|
||||||
@@ -26,16 +32,20 @@ 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:
|
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:
|
||||||
|
|
||||||
|
```sh
|
||||||
quote() { local q=${1//\'/\'\\\'\'}; echo "'$q'"; }
|
quote() { local q=${1//\'/\'\\\'\'}; echo "'$q'"; }
|
||||||
args="-lh $(quote "My File.txt")"
|
args="-lh $(quote "My File.txt")"
|
||||||
eval ls "$args" # Do not use unless you understand implications
|
eval ls "$args" # Do not use unless you understand implications
|
||||||
|
```
|
||||||
|
|
||||||
If you ever accidentally forget to use proper quotes, such as with:
|
If you ever accidentally forget to use proper quotes, such as with:
|
||||||
|
|
||||||
|
```sh
|
||||||
for f in *.txt; do
|
for f in *.txt; do
|
||||||
args="-lh '$1'" # Example security exploit
|
args="-lh '$1'" # Example security exploit
|
||||||
eval ls "$args" # Do not copy and use
|
eval ls "$args" # Do not copy and use
|
||||||
done
|
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 /`.
|
||||||
|
|
||||||
|
@@ -2,17 +2,21 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
if $(which epstopdf)
|
if $(which epstopdf)
|
||||||
then
|
then
|
||||||
echo "Found epstopdf"
|
echo "Found epstopdf"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
if which epstopdf
|
if which epstopdf
|
||||||
then
|
then
|
||||||
echo "Found epstopdf"
|
echo "Found epstopdf"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep foo file.txt | sed -e 's/foo/bar/g' > file.txt
|
grep foo file.txt | sed -e 's/foo/bar/g' > file.txt
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep foo file.txt | sed -e 's/foo/bar/g' > tmpfile && mv tmpfile file.txt
|
grep foo file.txt | sed -e 's/foo/bar/g' > tmpfile && mv tmpfile file.txt
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
#!/usr/bin/env bash -x
|
#!/usr/bin/env bash -x
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -x
|
set -x
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,19 +2,25 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
name=World cmd -m "Hello $name"
|
name=World cmd -m "Hello $name"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
export name=World
|
export name=World
|
||||||
cmd -m "Hello $name"
|
cmd -m "Hello $name"
|
||||||
|
```
|
||||||
|
|
||||||
To prevent setting the variable, this can also be done in a subshell:
|
To prevent setting the variable, this can also be done in a subshell:
|
||||||
|
|
||||||
|
```sh
|
||||||
(
|
(
|
||||||
export name=World
|
export name=World
|
||||||
cmd -m "Hello $name"
|
cmd -m "Hello $name"
|
||||||
) # 'name' does not leave this subshell
|
) # 'name' does not leave this subshell
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
gzip file[:digit:]*.txt
|
gzip file[:digit:]*.txt
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
gzip file[[:digit:]]*.txt
|
gzip file[[:digit:]]*.txt
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,16 +2,19 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
for dir in */
|
for dir in */
|
||||||
do
|
do
|
||||||
cd "$dir"
|
cd "$dir"
|
||||||
convert index.png index.jpg
|
convert index.png index.jpg
|
||||||
cd ..
|
cd ..
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
for dir in */
|
for dir in */
|
||||||
do
|
do
|
||||||
(
|
(
|
||||||
@@ -19,15 +22,18 @@
|
|||||||
convert index.png index.jpg
|
convert index.png index.jpg
|
||||||
)
|
)
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
for dir in */
|
for dir in */
|
||||||
do
|
do
|
||||||
cd "$dir" || exit
|
cd "$dir" || exit
|
||||||
convert index.png index.jpg
|
convert index.png index.jpg
|
||||||
cd ..
|
cd ..
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[ "$1" = "-v" && -z "$2" ]
|
[ "$1" = "-v" && -z "$2" ]
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[ "$1" = "-v" ] && [ -z "$2" ]
|
[ "$1" = "-v" ] && [ -z "$2" ]
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ "$1" = "-v" -a -z "$2" ]]
|
[[ "$1" = "-v" -a -z "$2" ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ "$1" = "-v" && -z "$2" ]]
|
[[ "$1" = "-v" && -z "$2" ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[ "$1" = "-v" || "$1" = "-help" ]
|
[ "$1" = "-v" || "$1" = "-help" ]
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[ "$1" = "-v" ] || [ "$1" = "-help" ]
|
[ "$1" = "-v" ] || [ "$1" = "-help" ]
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ "$1" = "-v" -o "$1" = "-help" ]]
|
[[ "$1" = "-v" -o "$1" = "-help" ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ "$1" = "-v" || "$1" = "-help" ]]
|
[[ "$1" = "-v" || "$1" = "-help" ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
rm -rf /usr /lib/nvidia-current/xorg/xorg
|
rm -rf /usr /lib/nvidia-current/xorg/xorg
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
rm -rf /usr/lib/nvidia-current/xorg/xorg
|
rm -rf /usr/lib/nvidia-current/xorg/xorg
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -18,6 +22,8 @@ Due to an accidental space, it deleted `/usr` instead of just the particular dir
|
|||||||
|
|
||||||
In cases of chroot, initramfs and similar, it's reasonable to delete otherwise important directories. Due to this, Shellcheck will not warn if the command contains `--`:
|
In cases of chroot, initramfs and similar, it's reasonable to delete otherwise important directories. Due to this, Shellcheck will not warn if the command contains `--`:
|
||||||
|
|
||||||
|
```sh
|
||||||
rm -rf -- /usr
|
rm -rf -- /usr
|
||||||
|
```
|
||||||
|
|
||||||
This is an arbitrary convention to allow deleting such directories without having to use a [[directive]] to silence the warning.
|
This is an arbitrary convention to allow deleting such directories without having to use a [[directive]] to silence the warning.
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
rm -rf "$STEAMROOT/"*
|
rm -rf "$STEAMROOT/"*
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
rm -rf "${STEAMROOT:?}/"*
|
rm -rf "${STEAMROOT:?}/"*
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
a=$(echo $?)
|
a=$(echo $?)
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
a="$?"
|
a="$?"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,14 +2,18 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
whoami
|
whoami
|
||||||
su
|
su
|
||||||
whoami
|
whoami
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
whoami
|
whoami
|
||||||
sudo whoami
|
sudo whoami
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,19 +2,23 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
sayhello() {
|
sayhello() {
|
||||||
echo "Hello $1"
|
echo "Hello $1"
|
||||||
}
|
}
|
||||||
sayhello
|
sayhello
|
||||||
|
```
|
||||||
|
|
||||||
`./myscript World` just prints "Hello " instead of "Hello World".
|
`./myscript World` just prints "Hello " instead of "Hello World".
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
sayhello() {
|
sayhello() {
|
||||||
echo "Hello $1"
|
echo "Hello $1"
|
||||||
}
|
}
|
||||||
sayhello "$@"
|
sayhello "$@"
|
||||||
|
```
|
||||||
|
|
||||||
`./myscript World` now prints "Hello World".
|
`./myscript World` now prints "Hello World".
|
||||||
|
|
||||||
@@ -26,9 +30,11 @@ If you want to process your script's parameters in a function, you have to expli
|
|||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
|
```sh
|
||||||
first() { second "$@"; }
|
first() { second "$@"; }
|
||||||
second() { echo "The first script parameter is: $1"; }
|
second() { echo "The first script parameter is: $1"; }
|
||||||
first "$@"
|
first "$@"
|
||||||
|
```
|
||||||
|
|
||||||
### Exceptions
|
### Exceptions
|
||||||
|
|
||||||
|
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
set var=42
|
set var=42
|
||||||
set var 42
|
set var 42
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
var=42
|
var=42
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ a <= b ]]
|
[[ a <= b ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ ! a > b ]]
|
[[ ! a > b ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,20 +2,26 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
PATH=/my/dir
|
PATH=/my/dir
|
||||||
cat "$PATH/myfile"
|
cat "$PATH/myfile"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
Good practice: always use lowercase for unexported variables.
|
Good practice: always use lowercase for unexported variables.
|
||||||
|
|
||||||
|
```sh
|
||||||
path=/my/dir
|
path=/my/dir
|
||||||
cat "$path/myfile"
|
cat "$path/myfile"
|
||||||
|
```
|
||||||
|
|
||||||
Bad practice: use another uppercase name.
|
Bad practice: use another uppercase name.
|
||||||
|
|
||||||
|
```sh
|
||||||
MYPATH=/my/dir
|
MYPATH=/my/dir
|
||||||
cat "$MYPATH/myfile"
|
cat "$MYPATH/myfile"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,25 +2,33 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
var=$@
|
var=$@
|
||||||
for i in $var; do ..; done
|
for i in $var; do ..; done
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
set -- Hello World
|
set -- Hello World
|
||||||
msg=$@
|
msg=$@
|
||||||
echo "You said $msg"
|
echo "You said $msg"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
var=( "$@" )
|
var=( "$@" )
|
||||||
for i in "${var[@]}"; do ..; done
|
for i in "${var[@]}"; do ..; done
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
set -- Hello World
|
set -- Hello World
|
||||||
msg=$*
|
msg=$*
|
||||||
echo "You said $msg"
|
echo "You said $msg"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo={1..9}
|
foo={1..9}
|
||||||
echo $foo
|
echo $foo
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
foo=( {1..9} )
|
foo=( {1..9} )
|
||||||
echo "${foo[@]}"
|
echo "${foo[@]}"
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep foo | wc -l
|
grep foo | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
grep -c foo
|
grep -c foo
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
@@ -14,10 +18,12 @@ This is purely a stylistic issue. `grep` can count lines without piping to `wc`.
|
|||||||
|
|
||||||
Note that in many cases, this number is only used to see whether there are matches (i.e. `> 0`). In these cases, it's better and more efficient to use `grep -q` and check its exit status:
|
Note that in many cases, this number is only used to see whether there are matches (i.e. `> 0`). In these cases, it's better and more efficient to use `grep -q` and check its exit status:
|
||||||
|
|
||||||
|
```sh
|
||||||
if grep -q pattern file
|
if grep -q pattern file
|
||||||
then
|
then
|
||||||
echo "The file contains the pattern"
|
echo "The file contains the pattern"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
### Exceptions
|
### Exceptions
|
||||||
|
|
||||||
|
@@ -2,19 +2,23 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
myarray=(foo bar)
|
myarray=(foo bar)
|
||||||
for f in $myarray
|
for f in $myarray
|
||||||
do
|
do
|
||||||
cat "$f"
|
cat "$f"
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
myarray=(foo bar)
|
myarray=(foo bar)
|
||||||
for f in "${myarray[@]}"
|
for f in "${myarray[@]}"
|
||||||
do
|
do
|
||||||
cat "$f"
|
cat "$f"
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,18 +2,22 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo foo >> file
|
echo foo >> file
|
||||||
date >> file
|
date >> file
|
||||||
cat stuff >> file
|
cat stuff >> file
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
{
|
{
|
||||||
echo foo
|
echo foo
|
||||||
date
|
date
|
||||||
cat stuff
|
cat stuff
|
||||||
} >> file
|
} >> file
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ $foo -eq "Y" ]]
|
[[ $foo -eq "Y" ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
[[ $foo = "Y" ]]
|
[[ $foo = "Y" ]]
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
alias whereami="echo $PWD"
|
alias whereami="echo $PWD"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
alias whereami='echo $PWD'
|
alias whereami='echo $PWD'
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
14
SC2140.md
14
SC2140.md
@@ -2,19 +2,27 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "<img src="foo.png" />" > file.html
|
echo "<img src="foo.png" />" > file.html
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
export "var"="42"
|
export "var"="42"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "<img src=\"foo.png\" />" > file.html
|
echo "<img src=\"foo.png\" />" > file.html
|
||||||
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
export "var=42"
|
export "var=42"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
@@ -28,13 +36,17 @@ This usually indicates one of:
|
|||||||
|
|
||||||
Without escaping, the inner two quotes of the sandwich (the end quote of the first section and the start quote of the second section) are no-ops. The following two statements are identical, so the quotes that were intended to be part of the html output are instead removed:
|
Without escaping, the inner two quotes of the sandwich (the end quote of the first section and the start quote of the second section) are no-ops. The following two statements are identical, so the quotes that were intended to be part of the html output are instead removed:
|
||||||
|
|
||||||
|
```sh
|
||||||
echo "<img src="foo.png" />" > file.html
|
echo "<img src="foo.png" />" > file.html
|
||||||
echo "<img src=foo.png />" > file.html
|
echo "<img src=foo.png />" > file.html
|
||||||
|
```
|
||||||
|
|
||||||
Similarly, these statements are identical, but work as intended:
|
Similarly, these statements are identical, but work as intended:
|
||||||
|
|
||||||
|
```sh
|
||||||
export "var"="42"
|
export "var"="42"
|
||||||
export "var=42"
|
export "var=42"
|
||||||
|
```
|
||||||
|
|
||||||
### Exceptions
|
### Exceptions
|
||||||
|
|
||||||
@@ -42,7 +54,9 @@ If you know that the quotes are ineffectual but you prefer it stylistically, you
|
|||||||
|
|
||||||
It's common not to realize that double quotes can span multiple elements, or to stylistically prefer to quote individual variables. For example, these statements are identical, but the first is laboriously and redundantly quoted:
|
It's common not to realize that double quotes can span multiple elements, or to stylistically prefer to quote individual variables. For example, these statements are identical, but the first is laboriously and redundantly quoted:
|
||||||
|
|
||||||
|
```sh
|
||||||
http://"$user":"$password"@"$host"/"$path"
|
http://"$user":"$password"@"$host"/"$path"
|
||||||
"http://$user:$password@$host/$path"
|
"http://$user:$password@$host/$path"
|
||||||
|
```
|
||||||
|
|
||||||
When ShellCheck detects the first style (i.e. the double quotes include only a single element each), it will suppress the warning.
|
When ShellCheck detects the first style (i.e. the double quotes include only a single element each), it will suppress the warning.
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
### Problematic code:
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
IFS="\t"
|
IFS="\t"
|
||||||
|
```
|
||||||
|
|
||||||
### Correct code:
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
IFS=$'\t'
|
IFS=$'\t'
|
||||||
|
```
|
||||||
|
|
||||||
### Rationale:
|
### Rationale:
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user