From 27eff07a216c362a69ac8d49009d3cbde4df0e9e Mon Sep 17 00:00:00 2001 From: Vidar Holen Date: Sun, 10 Jun 2018 19:09:23 -0700 Subject: [PATCH] Updated SC2145 (markdown) --- SC2145.md | 59 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/SC2145.md b/SC2145.md index b97ba40..311caab 100644 --- a/SC2145.md +++ b/SC2145.md @@ -24,39 +24,40 @@ If the intention is to provide each array element as a separate argument, put th ### 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: -```sh -subcommand_foo() { - echo "In foo" - echo "\$1 == $1 == aaa" - echo "\$2 == $2 == bbb" -} +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: -subcommand_bar() { - echo "In bar" -} + #!/bin/sh + fixed_fgrep() { grep -F "$@"; } + fixed_echo() { printf '%s\n' "$*"; } + fixed_seq() { echo "seq is not portable" >&2; return 1; } -subcommand_baz() { - echo "In baz" -} + if command -v "fixed_$1" > /dev/null 2>&1 + then + # shellcheck disable=SC2145 # I know how fixed_"$@" behaves and it's correct! + fixed_"$@" + else + "$@" + fi -main() { - case "$1" in - foo | bar | baz ) - subcommand_"$@" - ;; - * ) - printf "Error: %s\n" "Unrecognized command" - ;; - esac -} +Here's the same script without relying on this behavior: -main foo aaa bbb -main bar -``` + #!/bin/sh + 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`. \ No newline at end of file