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:
|
||||
|
||||
echo "$"
|
||||
```sh
|
||||
echo "$"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo "\$"
|
||||
```sh
|
||||
echo "\$"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
`$` is special in double quotes, but there are some cases where it's interpreted literally:
|
||||
|
16
SC1001.md
16
SC1001.md
@@ -2,19 +2,27 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo Yay \o/
|
||||
```sh
|
||||
echo Yay \o/
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
\git status
|
||||
```sh
|
||||
\git status
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo 'Yay \o/'
|
||||
```sh
|
||||
echo 'Yay \o/'
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
command git status
|
||||
```sh
|
||||
command git status
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
24
SC1007.md
24
SC1007.md
@@ -2,19 +2,27 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
# I want programs to show text in dutch!
|
||||
LANGUAGE= nl
|
||||
```sh
|
||||
# I want programs to show text in dutch!
|
||||
LANGUAGE= nl
|
||||
```
|
||||
|
||||
# I want to run the nl command with English error messages!
|
||||
LANGUAGE= nl
|
||||
```sh
|
||||
# I want to run the nl command with English error messages!
|
||||
LANGUAGE= nl
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
# I want programs to show text in dutch!
|
||||
LANGUAGE=nl
|
||||
```sh
|
||||
# I want programs to show text in dutch!
|
||||
LANGUAGE=nl
|
||||
```
|
||||
|
||||
# I want to run the nl command with English error messages!
|
||||
LANGUAGE='' nl
|
||||
```sh
|
||||
# I want to run the nl command with English error messages!
|
||||
LANGUAGE='' nl
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,11 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
for f in *; do echo "$f" done
|
||||
for f in *; do echo "$f" done
|
||||
|
||||
### Correct code:
|
||||
|
||||
for f in *; do echo "$f"; done
|
||||
for f in *; do echo "$f"; done
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo “hello world”
|
||||
```sh
|
||||
echo “hello world”
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo "hello world"
|
||||
```sh
|
||||
echo "hello world"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo ‘hello world’
|
||||
```sh
|
||||
echo ‘hello world’
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo 'hello world'
|
||||
```sh
|
||||
echo 'hello world'
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
12
SC1037.md
12
SC1037.md
@@ -2,13 +2,17 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo "Ninth parameter: $9"
|
||||
echo "Tenth parameter: $10"
|
||||
```sh
|
||||
echo "Ninth parameter: $9"
|
||||
echo "Tenth parameter: $10"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo "Ninth parameter: $9"
|
||||
echo "Tenth parameter: ${10}"
|
||||
```sh
|
||||
echo "Ninth parameter: $9"
|
||||
echo "Tenth parameter: ${10}"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC1038.md
16
SC1038.md
@@ -2,17 +2,21 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
while IFS= read -r line
|
||||
do
|
||||
```sh
|
||||
while IFS= read -r line
|
||||
do
|
||||
printf "%q\n" "$line"
|
||||
done <<(curl -s http://example.com)
|
||||
done <<(curl -s http://example.com)
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
while IFS= read -r line
|
||||
do
|
||||
```sh
|
||||
while IFS= read -r line
|
||||
do
|
||||
printf "%q\n" "$line"
|
||||
done < <(curl -s http://example.com)
|
||||
done < <(curl -s http://example.com)
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
14
SC1040.md
14
SC1040.md
@@ -4,23 +4,29 @@
|
||||
|
||||
Any code using `<<-` that is indented with spaces. `cat -T script` shows
|
||||
|
||||
```sh
|
||||
cat <<- foo
|
||||
Hello world
|
||||
foo
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
Code using `<<-` must be indented with tabs. `cat -T script` shows
|
||||
|
||||
^Icat <<- foo
|
||||
^I^IHello world
|
||||
^Ifoo
|
||||
```sh
|
||||
^Icat <<- foo
|
||||
^I^IHello world
|
||||
^Ifoo
|
||||
```
|
||||
|
||||
Or simply don't indent the end token:
|
||||
|
||||
```sh
|
||||
cat <<- foo
|
||||
Hello World
|
||||
foo
|
||||
foo
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
foo &; bar
|
||||
```sh
|
||||
foo &; bar
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
foo & bar
|
||||
```sh
|
||||
foo & bar
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC1065.md
16
SC1065.md
@@ -2,17 +2,21 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
foo(input) {
|
||||
```sh
|
||||
foo(input) {
|
||||
echo "$input"
|
||||
}
|
||||
foo("hello world");
|
||||
}
|
||||
foo("hello world");
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
foo() {
|
||||
```sh
|
||||
foo() {
|
||||
echo "$1"
|
||||
}
|
||||
foo "hello world"
|
||||
}
|
||||
foo "hello world"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC1066.md
16
SC1066.md
@@ -2,17 +2,23 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
$greeting="Hello World"
|
||||
```sh
|
||||
$greeting="Hello World"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
greeting="Hello World"
|
||||
```sh
|
||||
greeting="Hello World"
|
||||
```
|
||||
|
||||
Alternatively, if the goal was to assign to a variable whose name is in another variable (indirection), use `declare`:
|
||||
|
||||
name=foo
|
||||
declare "$name=hello world"
|
||||
echo "$foo"
|
||||
```sh
|
||||
name=foo
|
||||
declare "$name=hello world"
|
||||
echo "$foo"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
foo = 42
|
||||
```sh
|
||||
foo = 42
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
foo=42
|
||||
```sh
|
||||
foo=42
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
18
SC1077.md
18
SC1077.md
@@ -3,12 +3,16 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo "Your username is ´whoami´"
|
||||
```sh
|
||||
echo "Your username is ´whoami´"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo "Your username is $(whoami)" # Preferred
|
||||
echo "Your username is `whoami`" # Deprecated, will give [SC2006]
|
||||
```sh
|
||||
echo "Your username is $(whoami)" # Preferred
|
||||
echo "Your username is `whoami`" # Deprecated, will give [SC2006]
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -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:
|
||||
|
||||
echo "``Proprietary software is an injustice.´´ - Richard Stallman"
|
||||
```sh
|
||||
echo "``Proprietary software is an injustice.´´ - Richard Stallman"
|
||||
```
|
||||
|
||||
use single quotes instead:
|
||||
|
||||
echo '``Proprietary software is an injustice.´´ - Richard Stallman'
|
||||
```sh
|
||||
echo '``Proprietary software is an injustice.´´ - Richard Stallman'
|
||||
```
|
||||
|
||||
To nest forward ticks in command expansion, use `$(..)` instead of `` `..` ``.
|
24
SC1078.md
24
SC1078.md
@@ -2,13 +2,17 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
greeting="hello
|
||||
target="world"
|
||||
```sh
|
||||
greeting="hello
|
||||
target="world"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
greeting="hello"
|
||||
target="world"
|
||||
```sh
|
||||
greeting="hello"
|
||||
target="world"
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
var='multiline
|
||||
'value
|
||||
```sh
|
||||
var='multiline
|
||||
'value
|
||||
```
|
||||
|
||||
can be rewritten for readability and to remove the warning:
|
||||
|
||||
var='multiline
|
||||
value'
|
||||
```sh
|
||||
var='multiline
|
||||
value'
|
||||
```
|
||||
|
||||
As always `` `..` `` should be rewritten to ``$(..)``.
|
16
SC1081.md
16
SC1081.md
@@ -2,17 +2,21 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
If true
|
||||
Then
|
||||
```sh
|
||||
If true
|
||||
Then
|
||||
echo "hello"
|
||||
Fi
|
||||
Fi
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
if true
|
||||
then
|
||||
```sh
|
||||
if true
|
||||
then
|
||||
echo "hello"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC1083.md
16
SC1083.md
@@ -2,19 +2,26 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
rmf() { rm -f "$@" }
|
||||
```sh
|
||||
rmf() { rm -f "$@" }
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
eval echo \${foo}
|
||||
```sh
|
||||
eval echo \${foo}
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
rmf() { rm -f "$@"; }
|
||||
```sh
|
||||
rmf() { rm -f "$@"; }
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
eval "echo \${foo}"
|
||||
```sh
|
||||
eval "echo \${foo}"
|
||||
### Rationale:
|
||||
|
||||
Curly brackets are normally used as syntax in parameter expansion, command grouping and brace expansion.
|
||||
@@ -30,3 +37,4 @@ ShellCheck does not warn about `{}`, since this is frequently used with `find` a
|
||||
### Exceptions
|
||||
|
||||
This error is harmless when the curly brackets are supposed to be literal, in e.g. `awk {'print $1'}`. However, it's cleaner and less error prone to simply include them inside the quotes: `awk '{print $1}'`.
|
||||
```
|
||||
|
12
SC1084.md
12
SC1084.md
@@ -2,13 +2,17 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
!#/bin/sh
|
||||
echo "Hello World"
|
||||
```sh
|
||||
!#/bin/sh
|
||||
echo "Hello World"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
#!/bin/sh
|
||||
echo "Hello World"
|
||||
```sh
|
||||
#!/bin/sh
|
||||
echo "Hello World"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC1086.md
16
SC1086.md
@@ -2,17 +2,21 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
for $var in *
|
||||
do
|
||||
```sh
|
||||
for $var in *
|
||||
do
|
||||
echo "$var"
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
for var in *
|
||||
do
|
||||
```sh
|
||||
for var in *
|
||||
do
|
||||
echo "$var"
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo "$array[@]"
|
||||
```sh
|
||||
echo "$array[@]"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo "${array[@]}"
|
||||
```sh
|
||||
echo "${array[@]}"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC1088.md
16
SC1088.md
@@ -2,19 +2,27 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
grep ^(.*)\1$ file
|
||||
```sh
|
||||
grep ^(.*)\1$ file
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
var = myfunction(value)
|
||||
```sh
|
||||
var = myfunction(value)
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
grep '^(.*)\1$' file
|
||||
```sh
|
||||
grep '^(.*)\1$' file
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
var=$(myfunction value)
|
||||
```sh
|
||||
var=$(myfunction value)
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
28
SC1089.md
28
SC1089.md
@@ -2,18 +2,22 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
if true
|
||||
then
|
||||
```sh
|
||||
if true
|
||||
then
|
||||
echo hello
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
if true
|
||||
then
|
||||
```sh
|
||||
if true
|
||||
then
|
||||
echo hello
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
||||
var="foo
|
||||
if [[ $var = "bar ]
|
||||
then
|
||||
```sh
|
||||
var="foo
|
||||
if [[ $var = "bar ]
|
||||
then
|
||||
echo true
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
In this case, the `if` ends up inside the double quotes, leaving the `then` dangling.
|
||||
|
||||
|
10
SC1090.md
10
SC1090.md
@@ -2,12 +2,16 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
. "$(find_install_dir)/lib.sh"
|
||||
```sh
|
||||
. "$(find_install_dir)/lib.sh"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
# shellcheck source=src/lib.sh
|
||||
. "$(find_install_dir)/lib.sh"
|
||||
```sh
|
||||
# shellcheck source=src/lib.sh
|
||||
. "$(find_install_dir)/lib.sh"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
10
SC1091.md
10
SC1091.md
@@ -4,12 +4,16 @@ Reasons include: file not found, no permissions, not included on the command lin
|
||||
|
||||
### Problematic code:
|
||||
|
||||
source somefile
|
||||
```sh
|
||||
source somefile
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source somefile
|
||||
```sh
|
||||
# shellcheck disable=SC1091
|
||||
source somefile
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
10
SC1094.md
10
SC1094.md
@@ -2,12 +2,16 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
source mylib
|
||||
```sh
|
||||
source mylib
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
# shellcheck disable=SC1094
|
||||
source mylib
|
||||
```sh
|
||||
# shellcheck disable=SC1094
|
||||
source mylib
|
||||
```
|
||||
|
||||
(or fix `mylib`)
|
||||
|
||||
|
16
SC1097.md
16
SC1097.md
@@ -2,17 +2,23 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
var==value
|
||||
```sh
|
||||
var==value
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
Assignment:
|
||||
|
||||
var=value
|
||||
```sh
|
||||
var=value
|
||||
```
|
||||
|
||||
Comparison:
|
||||
|
||||
[ "$var" = value ]
|
||||
```sh
|
||||
[ "$var" = value ]
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -26,4 +32,6 @@ ShellCheck has noticed that you're using `==` in an unexpected way. The two most
|
||||
|
||||
If you wanted to assign a literal equals sign, use quotes to make this clear:
|
||||
|
||||
var="=sum(A1:A10)"
|
||||
```sh
|
||||
var="=sum(A1:A10)"
|
||||
```
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
eval $var=(a b)
|
||||
```sh
|
||||
eval $var=(a b)
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
eval "$var=(a b)"
|
||||
```sh
|
||||
eval "$var=(a b)"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC1099.md
16
SC1099.md
@@ -2,17 +2,21 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
while sleep 1
|
||||
do# show time
|
||||
```sh
|
||||
while sleep 1
|
||||
do# show time
|
||||
date
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
while sleep 1
|
||||
do # show time
|
||||
```sh
|
||||
while sleep 1
|
||||
do # show time
|
||||
date
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
12
SC2001.md
12
SC2001.md
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
string="stirng" ; echo "$string" | sed -e "s/ir/ri/"
|
||||
```sh
|
||||
string="stirng" ; echo "$string" | sed -e "s/ir/ri/"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
string="stirng" ; echo "${string//ir/ri}"
|
||||
```sh
|
||||
string="stirng" ; echo "${string//ir/ri}"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -16,6 +20,8 @@ Let's assume somewhere earlier in your code you have put data into a variable (E
|
||||
|
||||
Occasionally a more complex sed substitution is required. For example, getting the last character of a string.
|
||||
|
||||
string="stirng" ; echo "$string" | sed -e "s/^.*\(.\)$/\1/"
|
||||
```sh
|
||||
string="stirng" ; echo "$string" | sed -e "s/^.*\(.\)$/\1/"
|
||||
```
|
||||
|
||||
This is a bit simple for the example and there are alternative ways of doing this in the shell, but this SC2001 flags on several of my crazy complex sed commands which are beyond the scope of this example. Utilizing some of the more complex capabilities of sed is required occasionally and it is safe to ignore SC2001.
|
||||
|
12
SC2003.md
12
SC2003.md
@@ -2,13 +2,17 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
i=$(expr 1 + 2)
|
||||
l=$(expr length "$var")
|
||||
```sh
|
||||
i=$(expr 1 + 2)
|
||||
l=$(expr length "$var")
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
i=$((1+2))
|
||||
l=${#var}
|
||||
```sh
|
||||
i=$((1+2))
|
||||
l=${#var}
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
20
SC2004.md
20
SC2004.md
@@ -2,20 +2,26 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo $(($n+1))
|
||||
```sh
|
||||
echo $(($n+1))
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo $((n+1))
|
||||
```sh
|
||||
echo $((n+1))
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
The `$` on regular variables in arithmetic contexts is unnecessary, and can even lead to subtle bugs. This is because the contents of `$((..))` is first expanded into a string, and then evaluated as an expression:
|
||||
|
||||
$ a='1+1'
|
||||
$ echo $(($a * 5)) # becomes 1+1*5
|
||||
6
|
||||
$ echo $((a * 5)) # evaluates as (1+1)*5
|
||||
10
|
||||
```sh
|
||||
$ a='1+1'
|
||||
$ echo $(($a * 5)) # becomes 1+1*5
|
||||
6
|
||||
$ echo $((a * 5)) # evaluates as (1+1)*5
|
||||
10
|
||||
```
|
||||
|
||||
The `$` is unavoidable for special variables like `$1` vs `1`, `$#` vs `#`. ShellCheck does not warn about these cases.
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code
|
||||
|
||||
echo "Current time: `date`"
|
||||
```sh
|
||||
echo "Current time: `date`"
|
||||
```
|
||||
|
||||
### Correct code
|
||||
|
||||
echo "Current time: $(date)"
|
||||
```sh
|
||||
echo "Current time: $(date)"
|
||||
```
|
||||
|
||||
### Rationale
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
find . | echo
|
||||
```sh
|
||||
find . | echo
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
find .
|
||||
```sh
|
||||
find .
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC2009.md
16
SC2009.md
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic Code:
|
||||
|
||||
ps ax | grep -v grep | grep "$service" > /dev/null
|
||||
```sh
|
||||
ps ax | grep -v grep | grep "$service" > /dev/null
|
||||
```
|
||||
|
||||
### Correct Code:
|
||||
|
||||
pgrep -f "$service" > /dev/null
|
||||
```sh
|
||||
pgrep -f "$service" > /dev/null
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -16,11 +20,15 @@ If you are just after a pid from a running program, then pgrep is a much safer a
|
||||
|
||||
What if you have the pid and you are looking for the matching program name?
|
||||
|
||||
pid=123; ps ax | grep "$pid"
|
||||
```sh
|
||||
pid=123; ps ax | grep "$pid"
|
||||
```
|
||||
|
||||
What if you want a range of the ps field, like from the 16th space to the end of the line?
|
||||
|
||||
ps ax | grep "$pid" | cut -d" " -f16-
|
||||
```sh
|
||||
ps ax | grep "$pid" | cut -d" " -f16-
|
||||
```
|
||||
|
||||
Both are valid cases where SC2009 is not valid.
|
||||
|
||||
|
20
SC2012.md
20
SC2012.md
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
ls -l | grep " $USER " | grep '\.txt$'
|
||||
```sh
|
||||
ls -l | grep " $USER " | grep '\.txt$'
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
find . -maxdepth 1 -name '*.txt' -user "$USER"
|
||||
```sh
|
||||
find . -maxdepth 1 -name '*.txt' -user "$USER"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -14,11 +18,13 @@
|
||||
|
||||
Here's an example:
|
||||
|
||||
$ ls -l
|
||||
total 0
|
||||
-rw-r----- 1 me me 0 Feb 5 20:11 foo?bar
|
||||
-rw-r----- 1 me me 0 Feb 5 2011 foo?bar
|
||||
-rw-r----- 1 me me 0 Feb 5 20:11 foo?bar
|
||||
```sh
|
||||
$ ls -l
|
||||
total 0
|
||||
-rw-r----- 1 me me 0 Feb 5 20:11 foo?bar
|
||||
-rw-r----- 1 me me 0 Feb 5 2011 foo?bar
|
||||
-rw-r----- 1 me me 0 Feb 5 20:11 foo?bar
|
||||
```
|
||||
|
||||
It shows three seemingly identical filenames, and did you spot the time format change? How it formats and what it redacts can differ between locale settings, `ls` version, and whether output is a tty.
|
||||
|
||||
|
46
SC2013.md
46
SC2013.md
@@ -2,24 +2,30 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
for line in $(cat file | grep -v '^ *#')
|
||||
do
|
||||
```sh
|
||||
for line in $(cat file | grep -v '^ *#')
|
||||
do
|
||||
echo "Line: $line"
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
grep -v '^ *#' < file | while IFS= read -r line
|
||||
do
|
||||
```sh
|
||||
grep -v '^ *#' < file | while IFS= read -r line
|
||||
do
|
||||
echo "Line: $line"
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
or without a subshell (bash, zsh, ksh):
|
||||
|
||||
while IFS= read -r line
|
||||
do
|
||||
```sh
|
||||
while IFS= read -r line
|
||||
do
|
||||
echo "Line: $line"
|
||||
done < <(grep -v '^ *#' < file)
|
||||
done < <(grep -v '^ *#' < file)
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -27,20 +33,26 @@ For loops by default (subject to `$IFS`) read word by word. Additionally, glob e
|
||||
|
||||
Given this text file:
|
||||
|
||||
foo *
|
||||
bar
|
||||
```sh
|
||||
foo *
|
||||
bar
|
||||
```
|
||||
|
||||
The for loop will print:
|
||||
|
||||
Line: foo
|
||||
Line: aardwark.jpg
|
||||
Line: bullfrog.jpg
|
||||
...
|
||||
```sh
|
||||
Line: foo
|
||||
Line: aardwark.jpg
|
||||
Line: bullfrog.jpg
|
||||
...
|
||||
```
|
||||
|
||||
The while loop will print:
|
||||
|
||||
Line: foo *
|
||||
Line: bar
|
||||
```sh
|
||||
Line: foo *
|
||||
Line: bar
|
||||
```
|
||||
|
||||
|
||||
### Exceptions
|
||||
|
14
SC2014.md
14
SC2014.md
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
find . -name '*.tar' -exec tar xf {} -C "$(dirname {})" \;
|
||||
```sh
|
||||
find . -name '*.tar' -exec tar xf {} -C "$(dirname {})" \;
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
find . -name '*.tar' -exec sh -c 'tar xf "$1" -C "$(dirname "$1")"' _ {} \;
|
||||
```sh
|
||||
find . -name '*.tar' -exec sh -c 'tar xf "$1" -C "$(dirname "$1")"' _ {} \;
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -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:
|
||||
|
||||
$ sh -c 'echo "$1 is in $(dirname "$1")"' _ "mydir/myfile"
|
||||
mydir/myfile is in mydir
|
||||
```sh
|
||||
$ sh -c 'echo "$1 is in $(dirname "$1")"' _ "mydir/myfile"
|
||||
mydir/myfile is in mydir
|
||||
```
|
||||
|
||||
This command can be executed by `find -exec`, with `{}` as the filename argument. It executes shell which interprets the inlined script once for each file. Note that the inlined script is single quoted, again to ensure that the expansion does not happen prematurely .
|
||||
|
||||
|
14
SC2015.md
14
SC2015.md
@@ -2,16 +2,20 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
[[ $dryrun ]] && echo "Would delete file" || rm file
|
||||
```sh
|
||||
[[ $dryrun ]] && echo "Would delete file" || rm file
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
if [[ $dryrun ]]
|
||||
then
|
||||
```sh
|
||||
if [[ $dryrun ]]
|
||||
then
|
||||
echo "Would delete file"
|
||||
else
|
||||
else
|
||||
rm file
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC2016.md
16
SC2016.md
@@ -2,13 +2,17 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
name=World
|
||||
echo 'Hello $name'
|
||||
```sh
|
||||
name=World
|
||||
echo 'Hello $name'
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
name=World
|
||||
echo "Hello $name"
|
||||
```sh
|
||||
name=World
|
||||
echo "Hello $name"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -18,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:
|
||||
|
||||
echo '$1 USD is '"$rate GBP"
|
||||
```sh
|
||||
echo '$1 USD is '"$rate GBP"
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
percent=$((count/total*100))
|
||||
```sh
|
||||
percent=$((count/total*100))
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
percent=$((count*100/total))
|
||||
```sh
|
||||
percent=$((count*100/total))
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo 'hello world' | tr 'hello' 'goodbye'
|
||||
```sh
|
||||
echo 'hello world' | tr 'hello' 'goodbye'
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo 'hello world' | sed -e 's/hello/goodbye/g'
|
||||
```sh
|
||||
echo 'hello world' | sed -e 's/hello/goodbye/g'
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -1,13 +1,17 @@
|
||||
# Note that unlike globs, o* here matches 'ooo' but not 'oscar'
|
||||
### Problematic code:
|
||||
|
||||
grep 'foo*'
|
||||
```sh
|
||||
grep 'foo*'
|
||||
```
|
||||
|
||||
when wanting to match `food` and `foosball`, but not `mofo` or `keyfob`.
|
||||
|
||||
### Correct code:
|
||||
|
||||
grep '^foo'
|
||||
```sh
|
||||
grep '^foo'
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
12
SC2024.md
12
SC2024.md
@@ -2,11 +2,11 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
sudo echo 'export FOO=bar' >> /etc/profile
|
||||
sudo echo 'export FOO=bar' >> /etc/profile
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo 'export FOO=bar' | sudo tee -a /etc/profile > /dev/null
|
||||
echo 'export FOO=bar' | sudo tee -a /etc/profile > /dev/null
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -18,13 +18,13 @@ There is nothing special about `tee`. It's just the simplest command that can bo
|
||||
|
||||
Truncating:
|
||||
|
||||
echo 'data' | sudo dd of=file
|
||||
echo 'data' | sudo sed 'w file'
|
||||
echo 'data' | sudo dd of=file
|
||||
echo 'data' | sudo sed 'w file'
|
||||
|
||||
Appending:
|
||||
|
||||
echo 'data' | sudo awk '{ print $0 >> "file" }'
|
||||
echo 'data' | sudo sh -c 'cat >> file'
|
||||
echo 'data' | sudo awk '{ print $0 >> "file" }'
|
||||
echo 'data' | sudo sh -c 'cat >> file'
|
||||
|
||||
|
||||
### Exceptions
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
PS1='\e[36m\$ \e(B\e[m'
|
||||
```sh
|
||||
PS1='\e[36m\$ \e(B\e[m'
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
PS1='\[\e[36m\]\$ \[\e(B\e[m\]'
|
||||
```sh
|
||||
PS1='\[\e[36m\]\$ \[\e(B\e[m\]'
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
32
SC2026.md
32
SC2026.md
@@ -2,33 +2,43 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
alias server_uptime='ssh $host 'uptime -p''
|
||||
```sh
|
||||
alias server_uptime='ssh $host 'uptime -p''
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
alias server_uptime='ssh $host '"'uptime -p'"
|
||||
```sh
|
||||
alias server_uptime='ssh $host '"'uptime -p'"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
In the first case, the user has four single quotes on a line, wishfully hoping that the shell will match them up as outer quotes around a string with literal single quotes:
|
||||
|
||||
# v--------match--------v
|
||||
alias server_uptime='ssh $host 'uptime -p''
|
||||
# ^--match--^
|
||||
```sh
|
||||
# v--------match--------v
|
||||
alias server_uptime='ssh $host 'uptime -p''
|
||||
# ^--match--^
|
||||
```
|
||||
|
||||
The shell, meanwhile, always terminates single quoted strings at the first possible single quote:
|
||||
|
||||
# v---match--v
|
||||
alias server_uptime='ssh $host 'uptime -p''
|
||||
# ^^
|
||||
```sh
|
||||
# v---match--v
|
||||
alias server_uptime='ssh $host 'uptime -p''
|
||||
# ^^
|
||||
```
|
||||
|
||||
Which is the same thing as `alias server_uptime='ssh $host uptime' -p`.
|
||||
|
||||
There is no way to nest single quotes. However, single quotes can be placed literally in double quotes, so we can instead concatenate a single quoted string and a double quoted string:
|
||||
|
||||
# v--match---v
|
||||
alias server_uptime='ssh $host '"'uptime -p'"
|
||||
# ^---match---^
|
||||
```sh
|
||||
# v--match---v
|
||||
alias server_uptime='ssh $host '"'uptime -p'"
|
||||
# ^---match---^
|
||||
```
|
||||
|
||||
This results in an alias with embedded single quotes.
|
||||
|
||||
|
12
SC2027.md
12
SC2027.md
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo "You enter "$HOSTNAME". You can smell the wumpus." >> /etc/issue
|
||||
```sh
|
||||
echo "You enter "$HOSTNAME". You can smell the wumpus." >> /etc/issue
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
echo "You enter $HOSTNAME. You can smell the wumpus." >> /etc/issue
|
||||
```sh
|
||||
echo "You enter $HOSTNAME. You can smell the wumpus." >> /etc/issue
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -14,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:
|
||||
|
||||
echo "You enter "$HOSTNAME". You can smell the wumpus."
|
||||
```sh
|
||||
echo "You enter "$HOSTNAME". You can smell the wumpus."
|
||||
|----------| |---------------------------|
|
||||
Quoted No quotes Quoted
|
||||
```
|
||||
|
||||
If the quotes were supposed to be literal, they should be escaped. If the quotes were supposed to quote an expansion (as in the example), they should be removed because this is already a double quoted string.
|
||||
|
||||
|
12
SC2028.md
12
SC2028.md
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo "Name:\t$value"
|
||||
```sh
|
||||
echo "Name:\t$value"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
printf "Name:\t%s\n" "$value"
|
||||
```sh
|
||||
printf "Name:\t%s\n" "$value"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -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
|
||||
|
||||
echo "\\t"
|
||||
```sh
|
||||
echo "\\t"
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
|
16
SC2029.md
16
SC2029.md
@@ -2,21 +2,29 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
ssh host "echo $HOSTNAME"
|
||||
```sh
|
||||
ssh host "echo $HOSTNAME"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
ssh host "echo \$HOSTNAME"
|
||||
```sh
|
||||
ssh host "echo \$HOSTNAME"
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
ssh host 'echo $HOSTNAME'
|
||||
```sh
|
||||
ssh host 'echo $HOSTNAME'
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
Bash expands all arguments that are not escaped/singlequoted. This means that the problematic code is identical to
|
||||
|
||||
ssh host "echo clienthostname"
|
||||
```sh
|
||||
ssh host "echo clienthostname"
|
||||
```
|
||||
|
||||
and will print out the client's hostname, not the server's hostname.
|
||||
|
||||
|
36
SC2031.md
36
SC2031.md
@@ -29,33 +29,45 @@ Here are some constructs that cause subshells (shellcheck may not warn about all
|
||||
|
||||
Pipelines:
|
||||
|
||||
subshell1 | subshell2 | subshell3 # Bash, Dash, Ash
|
||||
subshell1 | subshell2 | regular # Ksh, Zsh
|
||||
```sh
|
||||
subshell1 | subshell2 | subshell3 # Bash, Dash, Ash
|
||||
subshell1 | subshell2 | regular # Ksh, Zsh
|
||||
```
|
||||
|
||||
Command substitution:
|
||||
|
||||
regular "$(subshell1)" "`subshell2`"
|
||||
```sh
|
||||
regular "$(subshell1)" "`subshell2`"
|
||||
```
|
||||
|
||||
Process substitution:
|
||||
|
||||
regular <(subshell1) >(subshell2)
|
||||
```sh
|
||||
regular <(subshell1) >(subshell2)
|
||||
```
|
||||
|
||||
Some forms of grouping:
|
||||
|
||||
( subshell )
|
||||
{ regular; }
|
||||
```sh
|
||||
( subshell )
|
||||
{ regular; }
|
||||
```
|
||||
|
||||
Backgrounding:
|
||||
|
||||
subshell1 &
|
||||
subshell2 &
|
||||
```sh
|
||||
subshell1 &
|
||||
subshell2 &
|
||||
```
|
||||
|
||||
Anything executed by external processes:
|
||||
|
||||
find . -exec subshell1 {} \;
|
||||
find . -print0 | xargs -0 subshell2
|
||||
sudo subshell3
|
||||
su -c subshell4
|
||||
```sh
|
||||
find . -exec subshell1 {} \;
|
||||
find . -print0 | xargs -0 subshell2
|
||||
sudo subshell3
|
||||
su -c subshell4
|
||||
```
|
||||
|
||||
This applies not only to setting variables, but also setting shell options and changing directories.
|
||||
|
||||
|
16
SC2033.md
16
SC2033.md
@@ -2,12 +2,16 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
foo() { bar --baz "$@"; frob --baz "$@"; };
|
||||
find . -exec foo {} +
|
||||
```sh
|
||||
foo() { bar --baz "$@"; frob --baz "$@"; };
|
||||
find . -exec foo {} +
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
find . -exec sh -c 'bar --baz "$@"; frob --baz "$@";' -- {} +
|
||||
```sh
|
||||
find . -exec sh -c 'bar --baz "$@"; frob --baz "$@";' -- {} +
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -19,7 +23,9 @@ Instead, the function contents can be executed in a shell, either through `sh -c
|
||||
|
||||
If you're intentionally passing a word that happens to have the same name as a declared function, you can quote it to make shellcheck ignore it, e.g.
|
||||
|
||||
nobody() {
|
||||
```sh
|
||||
nobody() {
|
||||
sudo -u "nobody" "$@"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
34
SC2034.md
34
SC2034.md
@@ -2,13 +2,17 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
foo=42
|
||||
echo "$FOO"
|
||||
```sh
|
||||
foo=42
|
||||
echo "$FOO"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
foo=42
|
||||
echo "$foo"
|
||||
```sh
|
||||
foo=42
|
||||
echo "$foo"
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
||||
read _ last _ zip _ _ <<< "$str"
|
||||
echo "$last, $zip"
|
||||
```sh
|
||||
read _ last _ zip _ _ <<< "$str"
|
||||
echo "$last, $zip"
|
||||
```
|
||||
|
||||
or use a directive to disable the warning:
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
read first last email zip lat lng <<< "$str"
|
||||
echo "$last, $zip"
|
||||
```sh
|
||||
# shellcheck disable=SC2034
|
||||
read first last email zip lat lng <<< "$str"
|
||||
echo "$last, $zip"
|
||||
```
|
||||
|
||||
For indirection, there's not much you can do without rewriting to use arrays or similar:
|
||||
|
||||
bar=42 # will always appear unused
|
||||
foo=bar
|
||||
echo "${!foo}"
|
||||
```sh
|
||||
bar=42 # will always appear unused
|
||||
foo=bar
|
||||
echo "${!foo}"
|
||||
```
|
||||
|
||||
This is expected behavior, and not a bug. There is no good way to statically analyze indirection in shell scripts, just like static C analyzers have a hard time preventing segfaults.
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
sum=find | wc -l
|
||||
```sh
|
||||
sum=find | wc -l
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
sum=$(find | wc -l)
|
||||
```sh
|
||||
sum=$(find | wc -l)
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
find . -type f | xargs md5sum
|
||||
```sh
|
||||
find . -type f | xargs md5sum
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
find . -type f -print0 | xargs -0 md5sum
|
||||
```sh
|
||||
find . -type f -print0 | xargs -0 md5sum
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC2041.md
16
SC2041.md
@@ -2,17 +2,21 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
for i in 'seq 1 10'
|
||||
do
|
||||
```sh
|
||||
for i in 'seq 1 10'
|
||||
do
|
||||
echo "$i"
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
for i in $(seq 1 10)
|
||||
do
|
||||
```sh
|
||||
for i in $(seq 1 10)
|
||||
do
|
||||
echo "$i"
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
24
SC2043.md
24
SC2043.md
@@ -2,10 +2,12 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
for var in value
|
||||
do
|
||||
```sh
|
||||
for var in value
|
||||
do
|
||||
echo "$var"
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
||||
for var in /my/dir/* ; do echo "$var"; done
|
||||
```sh
|
||||
for var in /my/dir/* ; do echo "$var"; done
|
||||
```
|
||||
|
||||
To iterate over lines in a file or command output, use a while read loop instead:
|
||||
|
||||
mycommand | while IFS= read -r line; do echo "$line"; done
|
||||
```sh
|
||||
mycommand | while IFS= read -r line; do echo "$line"; done
|
||||
```
|
||||
|
||||
To iterate over *words* written to a command or function's stdout, instead of `for var in myfunction`, use
|
||||
|
||||
for var in $(myfunction); do echo "$var"; done
|
||||
```sh
|
||||
for var in $(myfunction); do echo "$var"; done
|
||||
```
|
||||
|
||||
To iterate over *words* in a variable, instead of `for var in myvariable`, use
|
||||
|
||||
for var in $myvariable; do echo "$var"; done
|
||||
```sh
|
||||
for var in $myvariable; do echo "$var"; done
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
56
SC2044.md
56
SC2044.md
@@ -2,13 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
for file in $(find mydir -mtime -7 -name '*.mp3')
|
||||
do
|
||||
```sh
|
||||
for file in $(find mydir -mtime -7 -name '*.mp3')
|
||||
do
|
||||
let count++
|
||||
echo "Playing file no. $count"
|
||||
play "$file"
|
||||
done
|
||||
echo "Played $count files"
|
||||
done
|
||||
echo "Played $count files"
|
||||
```
|
||||
|
||||
This will fail for filenames containing spaces and similar, such as `My File.mp3`, and has a series of potential globbing issues depending on other filenames in the directory like (if you have `MyFile2.mp3` and `MyFile[2014].mp3`, the former file will play twice and the latter will not play at all).
|
||||
|
||||
@@ -18,13 +20,15 @@ There are many possible fixes, each with its pros and cons.
|
||||
|
||||
The most general fix (that requires the least amount of thinking to apply) is having `find` output a `\0` separated list of files and consuming them in a `while read` loop:
|
||||
|
||||
while IFS= read -r -d '' file
|
||||
do
|
||||
```sh
|
||||
while IFS= read -r -d '' file
|
||||
do
|
||||
let count++
|
||||
echo "Playing file no. $count"
|
||||
play "$file"
|
||||
done < <(find mydir -mtime -7 -name '*.mp3' -print0)
|
||||
echo "Played $count files"
|
||||
done < <(find mydir -mtime -7 -name '*.mp3' -print0)
|
||||
echo "Played $count files"
|
||||
```
|
||||
|
||||
In usage it's very similar to the `for` loop: it gets its output from a `find` statement, it executes a shell script body, it allows updating/aggregating variables, and the variables are available when the loop ends.
|
||||
|
||||
@@ -34,14 +38,16 @@ It requires Bash, and works with GNU, Busybox, OS X, FreeBSD and OpenBSD find, b
|
||||
|
||||
If you don't need `find` logic like `-mtime -7` and just use it to match globs recursively (all `*.mp3` files under a directory), you can instead use `globstar` and `nullglob` instead of `find`, and still use a `for` loop:
|
||||
|
||||
shopt -s globstar nullglob
|
||||
for file in mydir/**/*.mp3
|
||||
do
|
||||
```sh
|
||||
shopt -s globstar nullglob
|
||||
for file in mydir/**/*.mp3
|
||||
do
|
||||
let count++
|
||||
echo "Playing file no. $count"
|
||||
play "$file"
|
||||
done
|
||||
echo "Played $count files"
|
||||
done
|
||||
echo "Played $count files"
|
||||
```
|
||||
|
||||
This is bash specific.
|
||||
|
||||
@@ -50,15 +56,17 @@ This is bash specific.
|
||||
|
||||
If you need POSIX compliance, this is a fair approach:
|
||||
|
||||
find mydir ! -name "$(printf "*\n*")" -name '*.mp3' > tmp
|
||||
while IFS= read -r file
|
||||
do
|
||||
```sh
|
||||
find mydir ! -name "$(printf "*\n*")" -name '*.mp3' > tmp
|
||||
while IFS= read -r file
|
||||
do
|
||||
let count++
|
||||
echo "Playing file #$count"
|
||||
play "$file"
|
||||
done < tmp
|
||||
rm tmp
|
||||
echo "Played $count files"
|
||||
done < tmp
|
||||
rm tmp
|
||||
echo "Played $count files"
|
||||
```
|
||||
|
||||
The only problem is for filenames containing line feeds. A `! -name "$(printf "*\n*")"` has been added to simply skip these files, just in case there are any.
|
||||
|
||||
@@ -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:
|
||||
|
||||
# Simple and POSIX
|
||||
find mydir -name '*.mp3' -exec play {} \;
|
||||
```sh
|
||||
# Simple and POSIX
|
||||
find mydir -name '*.mp3' -exec play {} \;
|
||||
```
|
||||
|
||||
This does not allow things like `let counter++` because `let` is a shell builtin, not an external command.
|
||||
|
||||
@@ -77,10 +87,12 @@ This does not allow things like `let counter++` because `let` is a shell builtin
|
||||
|
||||
If we do need a shell script body but no aggregation, you can do the above but invoking `sh` (this is still POSIX):
|
||||
|
||||
find mydir -name '*.mp3' -exec sh -c '
|
||||
```sh
|
||||
find mydir -name '*.mp3' -exec sh -c '
|
||||
echo "Playing ${1%.mp3}"
|
||||
play "$1"
|
||||
' sh {} \;
|
||||
```
|
||||
|
||||
This would not be possible without `sh`, because `${1%.mp3}` is a shell construct that `find` can't evaluate by itself. If we had tried to `let counter++` in this loop, we would have found that the value never changes.
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
cp $* ~/dir
|
||||
```sh
|
||||
cp $* ~/dir
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
cp "$@" ~/dir
|
||||
```sh
|
||||
cp "$@" ~/dir
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
24
SC2051.md
24
SC2051.md
@@ -2,17 +2,17 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
for i in {1..$n}
|
||||
do
|
||||
for i in {1..$n}
|
||||
do
|
||||
echo "$i"
|
||||
done
|
||||
done
|
||||
|
||||
### Correct code:
|
||||
|
||||
for ((i=0; i<n; i++))
|
||||
do
|
||||
for ((i=0; i<n; i++))
|
||||
do
|
||||
echo "$i"
|
||||
done
|
||||
done
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -20,16 +20,16 @@ In Bash, brace expansion happens before variable expansion. This means that brac
|
||||
|
||||
For integers, use an arithmetic for loop instead. For zero-padded numbers or letters, use of eval may be warranted:
|
||||
|
||||
from="a" to="m"
|
||||
for c in $(eval "echo {$from..$to}"); do echo "$c"; done
|
||||
from="a" to="m"
|
||||
for c in $(eval "echo {$from..$to}"); do echo "$c"; done
|
||||
|
||||
or more carefully (if `from`/`to` could be user input, or if the brace expansion could have spaces):
|
||||
|
||||
from="a" to="m"
|
||||
while IFS= read -d '' -r c
|
||||
do
|
||||
from="a" to="m"
|
||||
while IFS= read -d '' -r c
|
||||
do
|
||||
echo "Read $c"
|
||||
done < <(eval "printf '%s\0' $(printf "{%q..%q}.jpg" "$from" "$to")")
|
||||
done < <(eval "printf '%s\0' $(printf "{%q..%q}.jpg" "$from" "$to")")
|
||||
|
||||
|
||||
### Exceptions
|
||||
|
16
SC2055.md
16
SC2055.md
@@ -2,17 +2,21 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
if [[ $1 != foo || $1 != bar ]]
|
||||
then
|
||||
```sh
|
||||
if [[ $1 != foo || $1 != bar ]]
|
||||
then
|
||||
echo "$1 is not foo or bar"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
if [[ $1 != foo && $1 != bar ]]
|
||||
then
|
||||
```sh
|
||||
if [[ $1 != foo && $1 != bar ]]
|
||||
then
|
||||
echo "$1 is not foo or bar"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
22
SC2059.md
22
SC2059.md
@@ -2,19 +2,25 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
printf "Hello, $NAME\n"
|
||||
```sh
|
||||
printf "Hello, $NAME\n"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
printf "Hello, %s\n" "$NAME"
|
||||
```sh
|
||||
printf "Hello, %s\n" "$NAME"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
`printf` interprets escape sequences and format specifiers in the format string. If variables are included, any escape sequences or format specifiers in the data will be interpreted too, when you most likely wanted to treat it as data. Example:
|
||||
|
||||
coverage='96%'
|
||||
printf "Unit test coverage: %s\n" "$coverage"
|
||||
printf "Unit test coverage: $coverage\n"
|
||||
```sh
|
||||
coverage='96%'
|
||||
printf "Unit test coverage: %s\n" "$coverage"
|
||||
printf "Unit test coverage: $coverage\n"
|
||||
```
|
||||
|
||||
The first printf writes `Unit test coverage: 96%`.
|
||||
|
||||
@@ -24,7 +30,9 @@ The second writes ``bash: printf: `\': invalid format character``
|
||||
|
||||
Sometimes you may actually want to interpret data as a format string, like in:
|
||||
|
||||
hexToAscii() { printf "\x$1"; }
|
||||
hexToAscii 21
|
||||
```sh
|
||||
hexToAscii() { printf "\x$1"; }
|
||||
hexToAscii 21
|
||||
```
|
||||
|
||||
Like all warnings, you can selectively silence this warning with a [directive](Directive).
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
tr -cd [:digit:]
|
||||
```sh
|
||||
tr -cd [:digit:]
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
tr -cd '[:digit:]'
|
||||
```sh
|
||||
tr -cd '[:digit:]'
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
grep '*foo*'
|
||||
```sh
|
||||
grep '*foo*'
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
grep 'foo' # or more explicitly, grep '.*foo.*'
|
||||
```sh
|
||||
grep 'foo' # or more explicitly, grep '.*foo.*'
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
trap "echo \"Finished on $(date)\"" EXIT
|
||||
```sh
|
||||
trap "echo \"Finished on $(date)\"" EXIT
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
trap 'echo "Finished on $(date)"' EXIT
|
||||
```sh
|
||||
trap 'echo "Finished on $(date)"' EXIT
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
30
SC2066.md
30
SC2066.md
@@ -2,30 +2,40 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
for s in "$(mycommand)"; do echo "$s"; done
|
||||
```sh
|
||||
for s in "$(mycommand)"; do echo "$s"; done
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
The correct code depends on your intention. Let's say you're in a directory with the files `file.png` and `My cat.png`, and you want to loop over a command that outputs (or variable that contains):
|
||||
|
||||
hello world
|
||||
My *.png
|
||||
```sh
|
||||
hello world
|
||||
My *.png
|
||||
```
|
||||
|
||||
#### Loop over each line without globbing (`hello world`, `My *.png`)
|
||||
|
||||
mycommand | while IFS= read -r s; do echo "$s"; done
|
||||
```sh
|
||||
mycommand | while IFS= read -r s; do echo "$s"; done
|
||||
```
|
||||
|
||||
#### Loop over each word with globbing (`hello`, `world`, `My`, `file.png`, `My cat.png`):
|
||||
|
||||
# relies on the fact that IFS by default contains space-tab-linefeed
|
||||
for s in $(mycommand); do echo "$s"; done
|
||||
```sh
|
||||
# relies on the fact that IFS by default contains space-tab-linefeed
|
||||
for s in $(mycommand); do echo "$s"; done
|
||||
```
|
||||
|
||||
#### Loop over each line with globbing (`hello world`, `My cat.png`)
|
||||
|
||||
# explicitly set IFS to contain only a line feed
|
||||
IFS='
|
||||
'
|
||||
for s in $(mycommand); do echo "$s"; done
|
||||
```sh
|
||||
# explicitly set IFS to contain only a line feed
|
||||
IFS='
|
||||
'
|
||||
for s in $(mycommand); do echo "$s"; done
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
20
SC2067.md
20
SC2067.md
@@ -2,13 +2,17 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
find . -type f -exec shellcheck {} | wc -l \;
|
||||
find . -exec echo {} ;
|
||||
```sh
|
||||
find . -type f -exec shellcheck {} | wc -l \;
|
||||
find . -exec echo {} ;
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
find . -type f -exec sh -c 'shellcheck "$1" | wc -l' -- {} \;
|
||||
find . -exec echo {} \;
|
||||
```sh
|
||||
find . -type f -exec sh -c 'shellcheck "$1" | wc -l' -- {} \;
|
||||
find . -exec echo {} \;
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
@@ -18,11 +22,15 @@
|
||||
|
||||
To instead go through each file and run `foo file && bar file` on it, invoke a shell that can interpret `&&`:
|
||||
|
||||
find . -exec sh 'foo "$1" && bar "$1"' -- {} \;
|
||||
```sh
|
||||
find . -exec sh 'foo "$1" && bar "$1"' -- {} \;
|
||||
```
|
||||
|
||||
You can also use find `-a` instead of shell `&&`:
|
||||
|
||||
find . -exec foo {} \; -a -exec bar {} \;
|
||||
```sh
|
||||
find . -exec foo {} \; -a -exec bar {} \;
|
||||
```
|
||||
|
||||
This will have the same effect (`-a` is also the default when two commands are specified, and can therefore be omitted).
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
cp $@ ~/dir
|
||||
```sh
|
||||
cp $@ ~/dir
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
cp "$@" ~/dir
|
||||
```sh
|
||||
cp "$@" ~/dir
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
firefox 2>&1 > /dev/null
|
||||
```sh
|
||||
firefox 2>&1 > /dev/null
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
firefox > /dev/null 2>&1
|
||||
```sh
|
||||
firefox > /dev/null 2>&1
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
36
SC2070.md
36
SC2070.md
@@ -2,40 +2,48 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
if [ -n $var ]
|
||||
then
|
||||
```sh
|
||||
if [ -n $var ]
|
||||
then
|
||||
echo "var has a value"
|
||||
else
|
||||
else
|
||||
echo "var is empty"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
In bash/ksh:
|
||||
|
||||
if [[ -n $var ]]
|
||||
then
|
||||
```sh
|
||||
if [[ -n $var ]]
|
||||
then
|
||||
echo "var has a value"
|
||||
else
|
||||
else
|
||||
echo "var is empty"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
In POSIX:
|
||||
|
||||
if [ -n "$var" ]
|
||||
then
|
||||
```sh
|
||||
if [ -n "$var" ]
|
||||
then
|
||||
echo "var has a value"
|
||||
else
|
||||
else
|
||||
echo "var is empty"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
|
||||
### Rationale:
|
||||
|
||||
When `$var` is unquoted, a blank value will cause it to wordsplit and disappear. If `$var` is empty, these two statements are identical:
|
||||
|
||||
[ -n $var ]
|
||||
[ -n ]
|
||||
```sh
|
||||
[ -n $var ]
|
||||
[ -n ]
|
||||
```
|
||||
|
||||
`[ string ]` is shorthand for testing if a string is empty. This is still true if `string` happens to be `-n`. `[ -n ]` is therefore true, and by extension so is `[ -n $var ]`.
|
||||
|
||||
|
10
SC2072.md
10
SC2072.md
@@ -2,12 +2,16 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
[[ 2 -lt 3.14 ]]
|
||||
```sh
|
||||
[[ 2 -lt 3.14 ]]
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
[[ 200 -lt 314 ]] # Use fixed point math
|
||||
[[ $(echo "2 < 3.14" | bc) == 1 ]] # Use bc
|
||||
```sh
|
||||
[[ 200 -lt 314 ]] # Use fixed point math
|
||||
[[ $(echo "2 < 3.14" | bc) == 1 ]] # Use bc
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
[[ $foo =~ "^fo+bar$" ]]
|
||||
```sh
|
||||
[[ $foo =~ "^fo+bar$" ]]
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
[[ $foo =~ ^fo+bar$ ]]
|
||||
```sh
|
||||
[[ $foo =~ ^fo+bar$ ]]
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
[[ 0=1 ]]
|
||||
```sh
|
||||
[[ 0=1 ]]
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
[[ 0 = 1 ]]
|
||||
```sh
|
||||
[[ 0 = 1 ]]
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
44
SC2082.md
44
SC2082.md
@@ -2,35 +2,43 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
var_1="hello world"
|
||||
n=1
|
||||
echo "${var_$n}"
|
||||
```sh
|
||||
var_1="hello world"
|
||||
n=1
|
||||
echo "${var_$n}"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
Bash/ksh:
|
||||
|
||||
# Use arrays instead of dynamic names
|
||||
declare -a var
|
||||
var[1]="hello world"
|
||||
n=1
|
||||
echo "${var[n]}"
|
||||
```sh
|
||||
# Use arrays instead of dynamic names
|
||||
declare -a var
|
||||
var[1]="hello world"
|
||||
n=1
|
||||
echo "${var[n]}"
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
# Expand variable names dynamically
|
||||
var_1="hello world"
|
||||
n=1
|
||||
name="var_$n"
|
||||
echo "${!name}"
|
||||
```sh
|
||||
# Expand variable names dynamically
|
||||
var_1="hello world"
|
||||
n=1
|
||||
name="var_$n"
|
||||
echo "${!name}"
|
||||
```
|
||||
|
||||
POSIX sh:
|
||||
|
||||
# Expand dynamically with eval
|
||||
var_1="hello world"
|
||||
n=1
|
||||
eval "tmp=\$var_$n"
|
||||
echo "${tmp}"
|
||||
```sh
|
||||
# Expand dynamically with eval
|
||||
var_1="hello world"
|
||||
n=1
|
||||
eval "tmp=\$var_$n"
|
||||
echo "${tmp}"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
44
SC2084.md
44
SC2084.md
@@ -2,41 +2,53 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
i=4
|
||||
$(( i++ ))
|
||||
```sh
|
||||
i=4
|
||||
$(( i++ ))
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
Bash, Ksh:
|
||||
|
||||
i=4
|
||||
(( i++ ))
|
||||
```sh
|
||||
i=4
|
||||
(( i++ ))
|
||||
```
|
||||
|
||||
POSIX (assuming `++` is supported):
|
||||
|
||||
i=4
|
||||
_=$(( i++ ))
|
||||
```sh
|
||||
i=4
|
||||
_=$(( i++ ))
|
||||
```
|
||||
|
||||
Alternative POSIX version that does not preserve the exit code:
|
||||
|
||||
: $(( i++ ))
|
||||
```sh
|
||||
: $(( i++ ))
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
`$((..))` expands to a number. If it's the only word on the line, the shell will try to execute this number as a command name:
|
||||
|
||||
$ i=4
|
||||
$ $(( i++ ))
|
||||
4: command not found
|
||||
$ echo $i
|
||||
5
|
||||
```sh
|
||||
$ i=4
|
||||
$ $(( i++ ))
|
||||
4: command not found
|
||||
$ echo $i
|
||||
5
|
||||
```
|
||||
|
||||
To avoid trying to execute the number as a command name, use one of the methods mentioned:
|
||||
|
||||
$ i=4
|
||||
$ _=$(( i++ ))
|
||||
$ echo $i
|
||||
5
|
||||
```sh
|
||||
$ i=4
|
||||
$ _=$(( i++ ))
|
||||
$ echo $i
|
||||
5
|
||||
```
|
||||
|
||||
### Exceptions:
|
||||
|
||||
|
25
SC2087.md
25
SC2087.md
@@ -2,15 +2,18 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
ssh host.example.com << EOF
|
||||
```sh
|
||||
ssh host.example.com << EOF
|
||||
echo "Logged in on $HOSTNAME"
|
||||
EOF
|
||||
EOF
|
||||
|
||||
### Correct code:
|
||||
|
||||
ssh host.example.com << "EOF"
|
||||
```sh
|
||||
ssh host.example.com << "EOF"
|
||||
echo "Logged in on $HOSTNAME"
|
||||
EOF
|
||||
EOF
|
||||
```
|
||||
|
||||
### 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,
|
||||
|
||||
ssh host << EOF
|
||||
```sh
|
||||
ssh host << EOF
|
||||
x="$(uname -a)"
|
||||
echo "$x"
|
||||
EOF
|
||||
EOF
|
||||
```
|
||||
|
||||
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"
|
||||
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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
ssh host.example.com << EOF
|
||||
```sh
|
||||
ssh host.example.com << EOF
|
||||
echo "Logged in on \$HOSTNAME from $HOSTNAME"
|
||||
EOF
|
||||
EOF
|
||||
```
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
rm "~/Desktop/$filename"
|
||||
```sh
|
||||
rm "~/Desktop/$filename"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
rm "$HOME/Desktop/$filename"
|
||||
```sh
|
||||
rm "$HOME/Desktop/$filename"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
34
SC2089.md
34
SC2089.md
@@ -2,21 +2,27 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
args='-lh "My File.txt"'
|
||||
ls $args
|
||||
```sh
|
||||
args='-lh "My File.txt"'
|
||||
ls $args
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
args=(-lh "My File.txt")
|
||||
ls "${args[@]}"
|
||||
```sh
|
||||
args=(-lh "My File.txt")
|
||||
ls "${args[@]}"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
Bash does not interpret data as code. Consider almost any other languages, such as Python:
|
||||
|
||||
print 1+1 # prints 2
|
||||
a="1+1"
|
||||
print a # prints 1+1, not 2
|
||||
```sh
|
||||
print 1+1 # prints 2
|
||||
a="1+1"
|
||||
print a # prints 1+1, not 2
|
||||
```
|
||||
|
||||
Here, `1+1` is Python syntax for adding numbers. However, passing a literal string containing this expression does not cause Python to interpret it, see the `+` and produce the calculated result.
|
||||
|
||||
@@ -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:
|
||||
|
||||
quote() { local q=${1//\'/\'\\\'\'}; echo "'$q'"; }
|
||||
args="-lh $(quote "My File.txt")"
|
||||
eval ls "$args" # Do not use unless you understand implications
|
||||
```sh
|
||||
quote() { local q=${1//\'/\'\\\'\'}; echo "'$q'"; }
|
||||
args="-lh $(quote "My File.txt")"
|
||||
eval ls "$args" # Do not use unless you understand implications
|
||||
```
|
||||
|
||||
If you ever accidentally forget to use proper quotes, such as with:
|
||||
|
||||
for f in *.txt; do
|
||||
```sh
|
||||
for f in *.txt; do
|
||||
args="-lh '$1'" # Example security exploit
|
||||
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 /`.
|
||||
|
||||
|
16
SC2091.md
16
SC2091.md
@@ -2,17 +2,21 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
if $(which epstopdf)
|
||||
then
|
||||
```sh
|
||||
if $(which epstopdf)
|
||||
then
|
||||
echo "Found epstopdf"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
if which epstopdf
|
||||
then
|
||||
```sh
|
||||
if which epstopdf
|
||||
then
|
||||
echo "Found epstopdf"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
grep foo file.txt | sed -e 's/foo/bar/g' > file.txt
|
||||
```sh
|
||||
grep foo file.txt | sed -e 's/foo/bar/g' > file.txt
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
grep foo file.txt | sed -e 's/foo/bar/g' > tmpfile && mv tmpfile file.txt
|
||||
```sh
|
||||
grep foo file.txt | sed -e 's/foo/bar/g' > tmpfile && mv tmpfile file.txt
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
10
SC2096.md
10
SC2096.md
@@ -2,12 +2,16 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
#!/usr/bin/env bash -x
|
||||
```sh
|
||||
#!/usr/bin/env bash -x
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
```sh
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC2097.md
16
SC2097.md
@@ -2,19 +2,25 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
name=World cmd -m "Hello $name"
|
||||
```sh
|
||||
name=World cmd -m "Hello $name"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
export name=World
|
||||
cmd -m "Hello $name"
|
||||
```sh
|
||||
export name=World
|
||||
cmd -m "Hello $name"
|
||||
```
|
||||
|
||||
To prevent setting the variable, this can also be done in a subshell:
|
||||
|
||||
(
|
||||
```sh
|
||||
(
|
||||
export name=World
|
||||
cmd -m "Hello $name"
|
||||
) # 'name' does not leave this subshell
|
||||
) # 'name' does not leave this subshell
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
gzip file[:digit:]*.txt
|
||||
```sh
|
||||
gzip file[:digit:]*.txt
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
gzip file[[:digit:]]*.txt
|
||||
```sh
|
||||
gzip file[[:digit:]]*.txt
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
24
SC2103.md
24
SC2103.md
@@ -2,32 +2,38 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
for dir in */
|
||||
do
|
||||
```sh
|
||||
for dir in */
|
||||
do
|
||||
cd "$dir"
|
||||
convert index.png index.jpg
|
||||
cd ..
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
|
||||
### Correct code:
|
||||
|
||||
for dir in */
|
||||
do
|
||||
```sh
|
||||
for dir in */
|
||||
do
|
||||
(
|
||||
cd "$dir" || exit
|
||||
convert index.png index.jpg
|
||||
)
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
for dir in */
|
||||
do
|
||||
```sh
|
||||
for dir in */
|
||||
do
|
||||
cd "$dir" || exit
|
||||
convert index.png index.jpg
|
||||
cd ..
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
[ "$1" = "-v" && -z "$2" ]
|
||||
```sh
|
||||
[ "$1" = "-v" && -z "$2" ]
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
[ "$1" = "-v" ] && [ -z "$2" ]
|
||||
```sh
|
||||
[ "$1" = "-v" ] && [ -z "$2" ]
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
[[ "$1" = "-v" -a -z "$2" ]]
|
||||
```sh
|
||||
[[ "$1" = "-v" -a -z "$2" ]]
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
[[ "$1" = "-v" && -z "$2" ]]
|
||||
```sh
|
||||
[[ "$1" = "-v" && -z "$2" ]]
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
[ "$1" = "-v" || "$1" = "-help" ]
|
||||
```sh
|
||||
[ "$1" = "-v" || "$1" = "-help" ]
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
[ "$1" = "-v" ] || [ "$1" = "-help" ]
|
||||
```sh
|
||||
[ "$1" = "-v" ] || [ "$1" = "-help" ]
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
[[ "$1" = "-v" -o "$1" = "-help" ]]
|
||||
```sh
|
||||
[[ "$1" = "-v" -o "$1" = "-help" ]]
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
[[ "$1" = "-v" || "$1" = "-help" ]]
|
||||
```sh
|
||||
[[ "$1" = "-v" || "$1" = "-help" ]]
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
12
SC2114.md
12
SC2114.md
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
rm -rf /usr /lib/nvidia-current/xorg/xorg
|
||||
```sh
|
||||
rm -rf /usr /lib/nvidia-current/xorg/xorg
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
rm -rf /usr/lib/nvidia-current/xorg/xorg
|
||||
```sh
|
||||
rm -rf /usr/lib/nvidia-current/xorg/xorg
|
||||
```
|
||||
|
||||
### 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 `--`:
|
||||
|
||||
rm -rf -- /usr
|
||||
```sh
|
||||
rm -rf -- /usr
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
rm -rf "$STEAMROOT/"*
|
||||
```sh
|
||||
rm -rf "$STEAMROOT/"*
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
rm -rf "${STEAMROOT:?}/"*
|
||||
```sh
|
||||
rm -rf "${STEAMROOT:?}/"*
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
a=$(echo $?)
|
||||
```sh
|
||||
a=$(echo $?)
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
a="$?"
|
||||
```sh
|
||||
a="$?"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
14
SC2117.md
14
SC2117.md
@@ -2,14 +2,18 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
whoami
|
||||
su
|
||||
whoami
|
||||
```sh
|
||||
whoami
|
||||
su
|
||||
whoami
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
whoami
|
||||
sudo whoami
|
||||
```sh
|
||||
whoami
|
||||
sudo whoami
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
24
SC2120.md
24
SC2120.md
@@ -2,19 +2,23 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
sayhello() {
|
||||
```sh
|
||||
sayhello() {
|
||||
echo "Hello $1"
|
||||
}
|
||||
sayhello
|
||||
}
|
||||
sayhello
|
||||
```
|
||||
|
||||
`./myscript World` just prints "Hello " instead of "Hello World".
|
||||
|
||||
### Correct code:
|
||||
|
||||
sayhello() {
|
||||
```sh
|
||||
sayhello() {
|
||||
echo "Hello $1"
|
||||
}
|
||||
sayhello "$@"
|
||||
}
|
||||
sayhello "$@"
|
||||
```
|
||||
|
||||
`./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:
|
||||
|
||||
first() { second "$@"; }
|
||||
second() { echo "The first script parameter is: $1"; }
|
||||
first "$@"
|
||||
```sh
|
||||
first() { second "$@"; }
|
||||
second() { echo "The first script parameter is: $1"; }
|
||||
first "$@"
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
|
10
SC2121.md
10
SC2121.md
@@ -2,12 +2,16 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
set var=42
|
||||
set var 42
|
||||
```sh
|
||||
set var=42
|
||||
set var 42
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
var=42
|
||||
```sh
|
||||
var=42
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
[[ a <= b ]]
|
||||
```sh
|
||||
[[ a <= b ]]
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
[[ ! a > b ]]
|
||||
```sh
|
||||
[[ ! a > b ]]
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
18
SC2123.md
18
SC2123.md
@@ -2,20 +2,26 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
PATH=/my/dir
|
||||
cat "$PATH/myfile"
|
||||
```sh
|
||||
PATH=/my/dir
|
||||
cat "$PATH/myfile"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
Good practice: always use lowercase for unexported variables.
|
||||
|
||||
path=/my/dir
|
||||
cat "$path/myfile"
|
||||
```sh
|
||||
path=/my/dir
|
||||
cat "$path/myfile"
|
||||
```
|
||||
|
||||
Bad practice: use another uppercase name.
|
||||
|
||||
MYPATH=/my/dir
|
||||
cat "$MYPATH/myfile"
|
||||
```sh
|
||||
MYPATH=/my/dir
|
||||
cat "$MYPATH/myfile"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
28
SC2124.md
28
SC2124.md
@@ -2,25 +2,33 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
var=$@
|
||||
for i in $var; do ..; done
|
||||
```sh
|
||||
var=$@
|
||||
for i in $var; do ..; done
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
set -- Hello World
|
||||
msg=$@
|
||||
echo "You said $msg"
|
||||
```sh
|
||||
set -- Hello World
|
||||
msg=$@
|
||||
echo "You said $msg"
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
var=( "$@" )
|
||||
for i in "${var[@]}"; do ..; done
|
||||
```sh
|
||||
var=( "$@" )
|
||||
for i in "${var[@]}"; do ..; done
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
set -- Hello World
|
||||
msg=$*
|
||||
echo "You said $msg"
|
||||
```sh
|
||||
set -- Hello World
|
||||
msg=$*
|
||||
echo "You said $msg"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
12
SC2125.md
12
SC2125.md
@@ -2,13 +2,17 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
foo={1..9}
|
||||
echo $foo
|
||||
```sh
|
||||
foo={1..9}
|
||||
echo $foo
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
foo=( {1..9} )
|
||||
echo "${foo[@]}"
|
||||
```sh
|
||||
foo=( {1..9} )
|
||||
echo "${foo[@]}"
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
16
SC2126.md
16
SC2126.md
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
grep foo | wc -l
|
||||
```sh
|
||||
grep foo | wc -l
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
grep -c foo
|
||||
```sh
|
||||
grep -c foo
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
||||
if grep -q pattern file
|
||||
then
|
||||
```sh
|
||||
if grep -q pattern file
|
||||
then
|
||||
echo "The file contains the pattern"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
|
20
SC2128.md
20
SC2128.md
@@ -2,19 +2,23 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
myarray=(foo bar)
|
||||
for f in $myarray
|
||||
do
|
||||
```sh
|
||||
myarray=(foo bar)
|
||||
for f in $myarray
|
||||
do
|
||||
cat "$f"
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
myarray=(foo bar)
|
||||
for f in "${myarray[@]}"
|
||||
do
|
||||
```sh
|
||||
myarray=(foo bar)
|
||||
for f in "${myarray[@]}"
|
||||
do
|
||||
cat "$f"
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
14
SC2129.md
14
SC2129.md
@@ -2,18 +2,22 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
echo foo >> file
|
||||
date >> file
|
||||
cat stuff >> file
|
||||
```sh
|
||||
echo foo >> file
|
||||
date >> file
|
||||
cat stuff >> file
|
||||
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
{
|
||||
```sh
|
||||
{
|
||||
echo foo
|
||||
date
|
||||
cat stuff
|
||||
} >> file
|
||||
} >> file
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
@@ -2,11 +2,15 @@
|
||||
|
||||
### Problematic code:
|
||||
|
||||
[[ $foo -eq "Y" ]]
|
||||
```sh
|
||||
[[ $foo -eq "Y" ]]
|
||||
```
|
||||
|
||||
### Correct code:
|
||||
|
||||
[[ $foo = "Y" ]]
|
||||
```sh
|
||||
[[ $foo = "Y" ]]
|
||||
```
|
||||
|
||||
### Rationale:
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user