2

Can you set the default value (parameter expansion) of a counting variable (shell aritmetic) so it doesn't have to be predefined in order to use it?

Even if the resulting string is longer and more complex that just defining the variable, as long as it remains the same syntax — in pure bash — from first to the last iteration, I'll take it as a yes.

To be clear, I'm thinking doing something like ${foo:- 1}F1 in (part or, built in) $((foo++)). Merge them…I don't know. Is it possible?

${foo++:- 1}?… $((${foo:- 1}++))?… banana+=1?

I'm not trying to accomplish anything, I'm just curious, so please forgive the lack of better examples.


F1: If I remember correctly, when a number (instead of word and assuming it doesn't consider 1→word→positionalparameter…man, I did not think this through!) follows :-, a space is needed between it an that number, hence why it's in there. I might be confusing it with substring expansion though that I'm sure it needs the space between : & -, not after -. In any case, if it's wrong, please correct me.

3
  • $((${foo:=1}, foo++))?
    – muru
    Commented May 29 at 1:24
  • I think what you're after is slicing an array
    – JayCravens
    Commented May 29 at 3:53
  • the part where you need a space is between : and - in something like ${var: -1} where you want the substring to start from a negative index (i.e. counting from the end). In ${var:-1} without the space, the standard interpretation of using a default value 1 applies. Compare empty= str=abc; echo ${empty:-1} ${str: -1}.
    – ilkkachu
    Commented May 29 at 6:52

1 Answer 1

9

Don't.

Using unsanitised data in an arithmetic expression in bash and most other Korn-like shells introduces arbitrary command execution vulnerabilities.

Shells on start up map their shell variables to the corresponding environment variables found in their environment. So it's critical that variables be defined to a sane value before you use them there.

If you start your script that does (( foo++ )) in an environment that has foo=a[$(reboot)1], that will reboot (assuming you have the permission to reboot; obviously one can do a lot worse than rebooting).

Example (here using a harmless uname command):

$ export foo='a[$(uname>&2)1]'
$ bash -c '(( foo++ ))'
Linux

So:

#! /bin/bash -
# initialise variables before use. Critical for those used in
# arithmetic expressions:
foo=1

# ....
(( foo++ ))

If you wanted to set the value of foo on first use, you could use the ${foo=initial} parameter expansion operator, but you'd still want to make sure it is initially unset and not imported from the environment:

#! /bin/bash -
# initialise variables before use. Critical for those used in
# arithmetic expressions:
unset -v foo

# ...
(( foo = ${foo=1} + 1 ))

unsetting it also has the benefit (well, side effect) of unexporting it so it won't be passed along to other commands executed within the script.

You can't use use ++ or += or other assignment operators with ${...} as that's an expansion, so while ${foo=1} will set $foo to 1 if previously unset, it will also expand to 1 (or the value of foo if previously set) and 1++ is not allowed in bash (and if it were, it would at best increment $1, not foo).


As to your ${var:-value} and spacing:

${var:-value} (same as ${var-value} except it also expands to value if $var is set but to the empty string) is from the Bourne shell in the late 70s.

In zsh (since 2.0 in 1991 when array support was added), where scalar and arrays are different types, $array[1,3] expands to the first to third elements of the array and $scalar[1,3] expands to the first to third characters of the scalar¹.

In ksh (since 1983), arrays and scalar were not initially really differentiated, and arrays where not really arrays, $scalar is actually ${scalar[0]}, so using zsh's ${scalar[first,last]} syntax to slice a scalar is out of the question in ksh.

ksh93 (a rewrite of ksh from scratch) added ${sparse_array[@]:offset:length} to expand to the first length elements of the array whose index is greater or equal to offset, and ${scalar:offset:length} (short for ${scalar[0]:offset:length}) to expand to the first length characters of $scalar starting with the offset-1th.

Support for negative offset (to count from the end) was added later, but if the offset starts with a -, it then conflicts with the Bourne ${var:-value} operator². ${var:-1:200} is still ${var:-default} with default being 1:200, so you need ${var: -1:200} or ${var:0-1:200} or ${var:(-1):200}, any arithmetic expression that yield -1 but starts with neither - nor +.

bash eventually added array support in 2.0 in 1996 but copied the ksh design rather than the zsh design, so has the same kind of issue.

zsh did eventually end up adding ${var:offset:length} but only for compatibility with ksh93/bash, mostly so one can interpret ksh/bash scripts that use that feature with its ksh emulation mode. You'd still want to use $var[first,last] there. When not in ksh emulation, ${var:offset:length} also conflicts with the $var:modifiers from csh (also from the 70s) in addition to Bourne's ${var:-value}.


¹ the braces are not needed unless the scalar you want to slice is an array member (${array[1][2,4]}). zsh contrary to ksh93 doesn't have multidimensional arrays. The fact that ksh93 has is another reason it would not be possible to slice a scalar with ${scalar[x,y]} there.

² btw, there's also a conflict with positive offsets if you use a + sign, not that you there's any reason you'd want to: ${var:+12} must be written ${var: +12}.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .