Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

zsh vs bash: how do parenthesis alter variable assignment behavior?

Tags:

bash

shell

zsh

I am having some troubles and misunderstanding about how are variable assignment and parenthesis handled in the different existing shells.

What is currently puzzling me is the following:

Always using the following command

./script.sh a b c d

when running the following code

#!/bin/zsh

bar=$@

for foo in $bar
do
    echo $foo
done

the output is

a b c d

and with

#!/bin/zsh

bar=($@)

for foo in $bar
do
    echo $foo
done

it is (what I initially wanted)

a
b
c
d

but using bash or sh

#!/bin/bash

bar=$@

for foo in $bar
do
    echo $foo
done

gives

a
b
c
d

and

#!/bin/bash

bar=($@)

for foo in $bar
do
    echo $foo
done

it is just

a

What is going on there ?

like image 309
deb0ch Avatar asked Apr 10 '26 23:04

deb0ch


1 Answers

Joint operations

For both of the shells involved, the examples given will assume an explicitly set argv list:

# this sets $1 to "first entry", $2 to "second entry", etc
$ set -- "first entry" "second entry" "third entry"

In both shells, declare -p can be used to emit the value of a variable name in unambiguous form, though how they represent that form can vary.

In bash

Expansion rules in bash are generally compatible with ksh and, where applicable, POSIX sh semantics. Being compatible with these shells requires that unquoted expansion perform string-splitting and glob expansion (replacing * with a list of files in the current directory, for instance).

Using parenthesis in a variable assignment makes it an array. Compare these three assignments:

# this sets arr_str="first entry second entry third entry"
$ arr_str=$@
$ declare -p arr_str
declare -- arr="first entry second entry third entry"

# this sets arr=( first entry second entry third entry )
$ arr=( $@ )
declare -a arr='([0]="first" [1]="entry" [2]="second" [3]="entry" [4]="third" [5]="entry")'

# this sets arr=( "first entry" "second entry" "third entry" )
$ arr=( "$@" )
$ declare -p arr
declare -a arr='([0]="first entry" [1]="second entry" [2]="third entry")'

Similarly, on expansion, quotes and sigils matter:

# quoted expansion, first item only
$ printf '%s\n' "$arr"
first entry

# unquoted expansion, first item only: that item is string-split into two separate args
$ printf '%s\n' $arr
first
entry

# unquoted expansion, all items: each word expanded into its own argument
$ printf '%s\n' ${arr[@]}
first
entry
second
entry
third
entry

# quoted expansion, all items: original arguments all preserved
$ printf '%s\n' "${arr[@]}"
first entry
second entry
third entry

In zsh

zsh does a great deal of magic to try to do what the user means, rather than what's compatible with historical shells (ksh, POSIX sh, etc). However, even there, doing the wrong thing can have results other than what you want:

# Assigning an array to a string still flattens it in zsh
$ arr_str=$@
$ declare -p arr_str
typeset arr_str='first entry second entry third entry'

# ...but quotes aren't needed to keep arguments together on array assignments.
$ arr=( $@ )
$ declare -p arr
typeset -a arr
arr=('first entry' 'second entry' 'third entry')

# in zsh, expanding an array always expands to all entries
$ printf '%s\n' $arr
first entry
second entry
third entry

# ...and unquoted string expansion doesn't do string-splitting by default:
$ printf '%s\n' $arr_str
first entry second entry third entry
like image 135
Charles Duffy Avatar answered Apr 12 '26 14:04

Charles Duffy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!