Bash Scripting

3 minutes

This came up when i was doing HackerRank challenges for Bash. The challenge called Compute the Average had the following problem statement:

Given N integers, compute their average, rounded to three decimal places. The first line contains an integer, N. Each of the following N lines contains a single integer.

My first attempt was to use `read`

with the `-r`

flag in a `while`

loop to read all lines one by one and ignore the `\`

character used to break up lines.

```
1read TOTAL_INTEGERS
2SUM=0
3
4# loops through all args, add them
5while read -r line
6do
7 INT="$line"
8 $(( SUM += INT ))
9done
10
11# calculate the average and pipe it to `bc` to get a floating point value
12AVERAGE=$(echo "$SUM/$TOTAL_INTEGERS" | bc -l)
13
14# print average rounded off to three decimal places
15printf "%.3f" $AVERAGE
```

This failed one of the tests where it turns out it wasn’t adding the last argument. Turns out that the `read`

command fails when the input is not terminated with a newline.

If there are some characters after the last line in the file (or to put it differently, if the last line is not terminated by a newline character), then read will read it but return false, leaving the broken partial line in the read variable(s). You can add a logical OR to the while test ref

So instead of `while read -r line`

, do `while read -r line || [[ -n $line ]]`

```
1read TOTAL_INTEGERS
2SUM=0
3
4# loops through all args, add them
5# while loops will run till we have complete lines of input
6# OR
7# when `-n` delimiter is reached (in this case the last value for $line)
8while read -r line || [[ -n $line ]]
9do
10 INT="$line"
11 $(( SUM += INT ))
12done
13
14# calculate the average and pipe it to `bc` to get a floating point value
15AVERAGE=$(echo "$SUM/$TOTAL_INTEGERS" | bc -l)
16
17# print average rounded off to three decimal places
18printf "%.3f" $AVERAGE
```

```
-r
If this option is given, backslash does not act as an escape character. The backslash is considered to be part of the line. In particular, a backslash-newline pair may not then be used as a line continuation.
-n nchars
read returns after reading nchars characters rather than waiting for a complete line of input, but honors a delimiter if fewer than nchars characters are read before the delimiter.
```

This worked as expected, but exceeded time limit (1s) by HackerRank. On to the next attempt using `readarray`

..

readarray Read lines from the standard input into the indexed array variable array, or from file descriptor fd if the -u option is supplied.

```
1read TOTAL_INTEGERS
2SUM=0
3
4readarray ARGS
5
6# loops through all args, add them
7for (( i = 0; i < TOTAL_INTEGERS; i++ ))
8do
9 $(( SUM += ARGS[i] ))
10done
11
12# calculate the average and pipe it to `bc` to get a floating point value
13AVERAGE=$(echo "$SUM/$TOTAL_INTEGERS" | bc -l)
14
15# print average rounded off to threbe decimal places
16printf "%.3f" $AVERAGE
```

This resulted in much simpler code and worked faster then the `read`

example. (This also makes sense why the first line of the input contains the total number of integers to be added)