Updated SC2145 (markdown)

Vidar Holen
2018-06-10 19:09:23 -07:00
parent bb58ca1d77
commit 27eff07a21

@@ -24,39 +24,40 @@ If the intention is to provide each array element as a separate argument, put th
### Exceptions ### Exceptions
Concatenating a string with an array can be used to make a command line interface with subcommands. The POSIX specified behavior of `$@` (and by extension arrays) as part of other strings is often unexpected:
To implement the subcommand interface, first pick a prefix like `subcommand_` below for the names of the functions that implement the subcommands. Then protect the parsing of the subcommands by listing them as patterns of a case statement. In the body of the case statement, a concatenation of the prefix with `"$@"` will invoke the subcommand function and pass the rest of the parameters to the function. > if the parameter being expanded was embedded within a word, the first field shall be joined with the beginning part of the original word and the last field shall be joined with the end part of the original word. In all other contexts the results of the expansion are unspecified. If there are no positional parameters, the expansion of '@' shall generate zero fields, even when '@' is within double-quotes; however, if the expansion is embedded within a word which contains one or more other parts that expand to a quoted null string, these null string(s) shall still produce an empty field, except that if the other parts are all within the same double-quotes as the '@', it is unspecified whether the result is zero fields or one empty field.
For example: If you're aware of this and intend to take advantage of it, you can ignore this warning. However, you can also usually also rewrite it into a less surprising form. For example, here's a wrapper script that uses this behavior to substitute certain commands by defining a function for them:
```sh
subcommand_foo() {
echo "In foo"
echo "\$1 == $1 == aaa"
echo "\$2 == $2 == bbb"
}
subcommand_bar() { #!/bin/sh
echo "In bar" fixed_fgrep() { grep -F "$@"; }
} fixed_echo() { printf '%s\n' "$*"; }
fixed_seq() { echo "seq is not portable" >&2; return 1; }
subcommand_baz() { if command -v "fixed_$1" > /dev/null 2>&1
echo "In baz" then
} # shellcheck disable=SC2145 # I know how fixed_"$@" behaves and it's correct!
fixed_"$@"
else
"$@"
fi
main() { Here's the same script without relying on this behavior:
case "$1" in
foo | bar | baz )
subcommand_"$@"
;;
* )
printf "Error: %s\n" "Unrecognized command"
;;
esac
}
main foo aaa bbb #!/bin/sh
main bar fixed_fgrep() { grep -F "$@"; }
``` fixed_echo() { printf '%s\n' "$*"; }
fixed_seq() { echo "seq is not portable" >&2; return 1; }
cmd="$1"
shift
if command -v "fixed_$cmd" > /dev/null 2>&1
then
# Perhaps more straight forward with fewer surprises:
"fixed_$cmd" "$@"
else
"$cmd" "$@"
fi
In the above example, inside the `main` function the value of `"$@"` is `( foo aaa bbb )`. The value of `subcommand_"$@"` after expansion is `( subcommand_foo aaa bbb )`, which the shell interprets as a call to `subcommand_foo` with parameters `aaa` and `bbb`.