Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concept: Arrays #722

Merged
merged 9 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 37 additions & 9 deletions concepts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,44 @@ The [plan](http://forum.exercism.org/t/bash-syllabus-planning/11952)
8. arrays
- numeric and associative
- iteration
- namerefs

...
9. arithmetic

10. more about arrays

- concatenate to a string with `"${ary[*]}"`
- IFS
- how positional parameters are "array-like"
- passing an array to a function
- by value
```bash
myfunc() {
local copy=("$@")
...
}
myfunc "${ary[@]}"
```
- indirect variable syntax
```bash
myfunc() {
local varname=$1
local tmp="${1}[@]"
local copy=( "${!tmp}" )
...
}
myfunc "ary"
```
- namerefs
```bash
myfunc() {
local -n ref=$1
...
}
myfunc "ary"
```
- sublist syntax `${ary[@]:offeset:length}`

## More Concepts

- brace expansions and how it's different from patterns `/path/to/{foo,bar,baz}.txt`

Expand All @@ -69,13 +104,6 @@ x. I/O
- capturing stdout and stderr
- capturing stdout and stderr **into separate variables**

x. arithmetic

x. variables 2

- scope
- attributes: `-i` `-l` `-u`

x. option parsing with getopts

x. `set` command and "strict mode"
Expand Down
10 changes: 10 additions & 0 deletions concepts/arrays/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"authors": [
"glennj"
],
"contributors": [
"kotp",
"IsaacG"
],
"blurb": "Arrays in Bash programs."
}
283 changes: 283 additions & 0 deletions concepts/arrays/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
# About Arrays

Bash provides two types of parameters: "scalar" values and arrays.
A scalar value can be a string or a number; it represents a single "thing".

Most of the time, scalar values are sufficient to contain your data.
But sometimes, you need to store a list of scalar values.
In this case you will use an _array_.

There are two kinds of arrays:

* Numerically-indexed arrays map an integer key to a string value.
(Other languages might call this a "list" or a "sequence".)
* Associative arrays map a string key to a string value.
(Other languages might call this a "map" or a "dictionary".)

The first few sections of this document will cover numerically-indexed arrays (hereafter simply known as "arrays").
Details about associative arrays will show up at the end.

~~~~exercism/caution
Bash arrays can _only_ store scalar values.
There is no ability to create a two-dimensional array (array of arrays).
~~~~

## Declaring and Initializing an Array Variable

You can initialize an array by assigning a parenthesized list of elements (possibly empty) to an variable.

```bash
myarray=("one" "two" "three")
```

As is the case with every variable assignment, there must be no spaces around the equal sign.

Note that there are no comma separators between the elements; it is a whitespace-separated list of strings.

In fact, the amount of whitespace inside the parentheses is completely arbitrary.
Newlines are allowed; use as many as you want to improve readability.

```bash
myarray=(
"first element"
"second element"
"this is the last element"
)
```

Assigning a list of strings to an array variable in this manner will store the first string at index zero, the next string at index one, the next at index two, and so on.

It is possible to initialize a "sparse array" by specifying the indices you need.

```bash
raindrops=([3]="Pling" [5]="Plang [7]="Plong")
```

~~~~exercism/note
To reiterate: array indices are zero-based.
This snippet outputs "true":

```bash
myarray=("one" "two" "three")
[[ "${myarray[0]}" == "one" ]] && echo true || echo false
```
~~~~

You can use the `declare` (or `local` inside a function) command to specify that a variable will hold an array.

```bash
declare -a myarray
```

It is more idiomatic to just assign a (perhaps empty) parenthesized list to initialize the variable.

## Accessing Elements of an Array

As you saw in the note above, the syntax to access an array element is `${myarray[$index]}`.
The index is given in square brackets.
The curly braces are required.

~~~~exercism/note
For numerically-indexed arrays, the index is an arithmetic expression.
The Bash manual [states][arithmetic]:

> Within an arithmetic expression, shell variables may also be referenced by name without using the parameter expansion syntax.

This means that the `$` is not required for "simple" variable expansion.
You can access elements like this, which is a bit easier to read:

```bash
i=5
echo "${myarray[i]}"
# ..............^
```

[arithmetic]: https://www.gnu.org/software/bash/manual/bash.html#Shell-Arithmetic
~~~~

If the given index does not exist in the array, the result of the expansion is an empty string.

## Assigning to an Array

To assign to a particular array entry, use this syntax:

```bash
myarray[10]="hello"
```

To _append_ to an array, use the `+=` concatenating-assigment operator, and use parentheses:

```bash
myarray+=("new element 1" "new element 2")
```

## Iterating over an Array

You will typically need to iterate over the _values_ of an array.
Use a for loop (that you saw in the [Looping][looping] concept):

```bash
myarray=("one" "two" "three")
for value in "${myarray[@]}"; do
echo "$value"
done
```

Note the special syntax to expand the array: `"${myarray[@]}"`
This form of parameter expansion substitutes the array values as separate words.
It is somewhat equivalent to:

```bash
for value in "${myarray[0]}" "${myarray[1]}" ... "${myarray[n]}"
```

The `"${myarray[@]}"` form works with sparse arrays, expanding to only defined values.

This special expansion, where the index is an ampersand (`@`), is only special within double quotes.

#### Other Special Parameter Exapansions

Here are another couple of bits of special syntax that we need for iterating over an array.
These are described in the manual in [Shell Parameter Expansion][man-expn].

To find the _length_ of an array, use `${#myarray[@]}`, with a `#` before the name.
The "length" of an array means the number of elements in it.

To extract the list of _indices_ of an array, use `"${!myarray[@]}"`, with a `!` before the name and the expansion in double quotes.
For numerically-indexed arrays, the indices are expanded in numerical order.

### Iterating over Array Indices

Sometimes you need the index as well as the value.
There are a couple of ways to do this.

1. When you know the indices form an uninterrupted sequence, you can use an _arithmetic_ for loop.

```bash
for ((i = 0; i < ${#myarray[@]}; i++)); do
echo "$i -> ${myarray[i]}"
done
```

Note the `${#myarray[@]}` syntax, with the `#` character in front of the array name.
Since arrays use zero-based indexing, the array's last index is one less than the array length.

2. When the array is sparse, you can use this form:

```bash
for index in "${!raindrops[@]}"; do
echo "$index -> ${raindrops[index]}"
done
```

Note the `${!myarray[@]}` syntax, with the `!` character in front of the array name.

## Inspecting an Array

If you want to look at the contents of an array, use the `declare` command with the `-p` option:

```bash
a=("the 'first' element"
second
third
'the "last" element')
declare -p a
```

This outputs the contents with canonical shell quoting.

```bash
declare -a a=([0]="the 'first' element" [1]="second" [2]="third" [3]="the \"last\" element")
```

## Associative Arrays

To reiterate, associative arrays use strings for indices, not numbers.
Everything written above applies for associative arrays, but with small differences.
Generally, we use the term "key" not "index": associative arrays map keys to values.

### Declaring an Associative Array

Unlike arrays, associative arrays **must** be declared.
Use the `-A` option to `declare` (or `local` in a function):

```bash
declare -A mymap

myfunc() {
local -A my_local_map
# ...
}
```

### Initializing an Associative Array

The key-value pairs are specified within the parenthesized list:

```bash
mymap=([first]=foo [second]=bar [third]=baz)
declare -p mymap
```

This outputs:

```
declare -A mymap=([second]="bar" [first]="foo" [third]="baz")
```

~~~~exercism/note
Be aware that associative arrays are **unordered**.
~~~~

The declaration and initialization can happen in one line.

```bash
declare -A mymap=([first]=foo [second]=bar [third]=baz)
```

In recent Bash versions, the initialization can use a `(key value key value ...)` list.

```bash
declare -A mymap=(first foo second bar third baz)
```

### Accessing Values

The same syntax as before is used to access elements: `${mymap[$key]}`.

Associative array keys are not evaluated in an arithmetic context.
The usual parameter expansion syntax is required.

### Assigning to an Associative Array

Assignment uses the same syntax as before:

```bash
mymap[$key]="$value"
```

### Iterating

You can use a `for value in "${mymap[@]}"` loop to iterate.
However, you typically also want the key to go along with the value.

Use the `"${!mymap[@]}"` syntax we saw above.

```bash
for key in "${!mymap[@]}"; do
echo "$key -> ${mymap[$key]}"
done
```

This outputs

```
second -> bar
first -> foo
third -> baz
```

To re-emphasize, there is no order to the keys.

[looping]: https://exercism.org/tracks/bash/concepts/looping
[man-expn]: https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion
Loading