6

I have a Bash function that does some string manipulation on its arguments as a whole ("$@") by putting it in a local variable, something like this:

my_func() {
    local args="$@"
    echo "args: <$args>"
}

my_func "$@"

When I run this in Bash, args contains all of the arguments that were passed:

$ bash foo.sh foo bar baz
args: <foo bar baz>

However, if I run it in Dash, only the first argument is stored:

$ dash test.sh foo bar baz
args: <foo>

Reading the section on local in the Ubuntu Wiki's "Dash as /bin/sh" page, it seems that Dash is expanding the local args="$@" line like so:

local args=foo bar baz

and therefore only putting "foo" in args and declaring bar and baz as (local?) variables. In fact, if I add echo "bar: $bar" to my_func and run it with an = in the arguments it seems to confirm that I am adding variables:

$ foo.sh foo bar=baz
args: <foo>
bar: baz

All this to say, is there a way to get the Bash-like behaviour (of $args containing "foo bar baz") in Dash?

Harry Cutts
  • 242
  • 1
  • 10

1 Answers1

5

The expansion of $@ in local args="$@" is unspecified by the POSIX standard. The bash shell will create a single space-delimited string containing all the positional parameters as the value for the args variable, while dash will try to execute local args="$1" "$2" "$3" (etc.)

The zsh and ksh shells behave like bash (creating a single string out of the positional parameters, although zsh would use the first character of $IFS for the delimiter) while the yash shell behaves like dash, at least in their default configurations.

In your case, you should use

my_func () {
    local args
    args="$*"

    printf 'args: <%s>\n' "$args"
}

or

my_func () {
    local args="$*"

    printf 'args: <%s>\n' "$args"
}

I'm using $* here to make it obvious that I'm constructing a single string from a list of values. The string will contain the values of the positional parameters, delimited by the first character of $IFS (a space by default).

I'm also using printf to be sure to get the correct output of the user-supplied values (see Why is printf better than echo?).

Also, your script should use #!/bin/dash as the first line rather than #!/bin/sh as local is an extension to the standard sh syntax.

Kusalananda
  • 333,661
  • In which versions of dash? local foo=bar works in both dash-0.5.8 and the related busybox ash. –  Jul 25 '19 at 20:28
  • @mosvy I noticed. However, the manual does not mention that it is possible explicitly, and I'm now wondering whether this is a bug related to the expansion of "$@". – Kusalananda Jul 25 '19 at 20:29
  • Yep, that works, thank you! Yes, I guess it should be #!/bin/dash, but I hadn't put that much thought into it since it's just an example script, similarly for echo use. – Harry Cutts Jul 25 '19 at 20:33
  • The expansion of "$@" is unspecified in foo="$@", according to the standard. –  Jul 25 '19 at 20:34
  • @mosvy Yes, you are correct. Better to use "$*" when creating a single string out of the positional parameters. – Kusalananda Jul 25 '19 at 20:35
  • It's not true that zsh behaves like bash: Try foo(){ local IFS=/; local foo=$@; echo "{$foo}"; }; foo 1 2 3 in zsh and bash. Basically, zsh, yash and dash work the same, different from bash if not tripping on the non/badly implemented local feature. They will treat foo=$@ exactly like foo=$*. –  Jul 26 '19 at 15:07
  • @mosvy Huh? If your code shows anything, it shows that not any shell treats the expansion as any other shell when modifying $IFS. What I meant was that zsh and bash both creates a single string out of all positional parameters (yes, zsh will use $IFS and bash does not), while the others does the expansion totally differently, leading to a potential error in dash and to just seeing $1 in yash. – Kusalananda Jul 26 '19 at 15:25
  • I've re-read my comment and my code shows exactly what I said. All the shells will join the positional arguments into a single string in foo=$@, some of them using the 1st char of IFS (exactly as foo=$*), and some of them using a space. Not all shells support local and some support it badly, an issue that is orthogonal to this. –  Jul 26 '19 at 15:39
  • @mosvy Your code returns {1} for yash, showing that it does not create a string out of all positional parameters. Likewise, dash emits an error because it ends up executing local foo=1 2 3 and declaring 2 local makes no sense to it. – Kusalananda Jul 26 '19 at 15:46
  • You're mixing up two separate issues a) how does foo=$@ work in different shells and b) how well is local supported. If you want a testcase for a) have it here: foo(){ IFS=/; foo=$@; echo "$foo"; }; foo 1 2 3. If you want a testcase for b): foo(){ local foo=$1; echo "$foo"; }; foo "1 2 3". –  Jul 26 '19 at 15:59
  • @mosvy Well, the only thing I've touched upon in the answer is the expansion of $@. local is not supported in ksh93, so one would simply use typeset instead. That is not the issue I'm addressing, and that is not the issue cause a problem for this user. – Kusalananda Jul 26 '19 at 16:02