How to add a value into the middle of an array? - arrays

Hello I have the following array:
array=(1 2 3 4 5 7 8 9 12 13)
I execute this for loop:
for w in ${!array[#]}
do
comp=$(echo "${array[w+1]} - ${array[w]} " | bc)
if [ $comp = 1 ]; then
/*???*/
else
/*???*/
fi
done
What I would like to do is to insert a value when the difference between two consecutive elements is not = 1
How can I do it?
Many thanks.

Just create a loop from the minimum to the maximum values and fill the gaps:
array=(1 2 3 4 5 7 8 9 12 13)
min=${array[0]}
max=${array[-1]}
new_array=()
for ((i=min; i<=max; i++)); do
if [[ " ${array[#]} " =~ " $i " ]]; then
new_array+=($i)
else
new_array+=(0)
fi
done
echo "${new_array[#]}"
This creates a new array $new_array with the values:
1 2 3 4 5 0 7 8 9 0 0 12 13
This uses the trick in Check if an array contains a value.

You can select parts of the original array with ${arr[#]:index:count}.
Select the start, insert a new element, add the end.
To insert an element after index i=5 (the fifth element)
$ array=(1 2 3 4 5 7 8 9 12 13)
$ i=5
$ arr=("${array[#]:0:i}") ### take the start of the array.
$ arr+=( 0 ) ### add a new value ( may use $((i+1)) )
$ arr+=("${array[#]:i}") ### copy the tail of the array.
$ array=("${arr[#]}") ### transfer the corrected array.
$ printf '<%s>' "${array[#]}"; echo
<1><2><3><4><5><6><7><8><9><12><13>
To process all the elements, just do a loop:
#!/bin/bash
array=(1 2 3 4 5 7 8 9 12 13)
for (( i=1;i<${#array[#]};i++)); do
if (( array[i] != i+1 ));
then arr=("${array[#]:0:i}") ### take the start of the array.
arr+=( "0" ) ### add a new value
arr+=("${array[#]:i}") ### copy the tail of the array.
# echo "head $i ${array[#]:0:i}" ### see the array.
# echo "tail $i ${array[#]:i}"
array=("${arr[#]}") ### transfer the corrected array.
fi
done
printf '<%s>' "${array[#]}"; echo
$ chmod u+x ./script.sh
$ ./script.sh
<1><2><3><4><5><0><7><8><9><10><0><0><13>

There does not seem to be a way to insert directly into an array. You can append elements to another array instead though:
result=()
for w in ${!array[#]}; do
result+=("${array[w]}")
comp=$(echo "${array[w+1]} - ${array[w]} " | bc)
if [ $comp = 1 ]; then
/* probably leave empty */
else
/* handle missing digits */
fi
done
As a final step, you can assign result back to the original array.

Related

'if' in array w/ Shell Script

I have an array with different values, and if the value is 3 (integer) this value will be exchanged for 999 (integer). But I have a syntax problem in the 'if' block of the array.
The correct return would be: 1 999 5 7 9 999
vetor=(1 3 5 7 9 3)
for i in ${vetor[*]}
do
if [[ ${vetor[i]} = 3 ]]; then
${vetor[i]} = 999
fi
echo $i
done
This produces the correct output in Bash:
vetor=(1 3 5 7 9 3);
for i in ${!vetor[*]};
do
if [[ ${vetor[i]} -eq 3 ]]; then
vetor[i]=999;
fi;
echo ${vetor[i]};
done
I added ! in the for loop expression to get the indices of vetor instead of the values, and I removed the ${} around the assignment in the if condition (this was giving "if 3 is not a typo" warnings). Also changed the echo to get the value at vetor[i] instead of printing the index.

How to identify the position of a sequence of elements in an array in bash

I'm learning bash and I'm trying identify a sequence elements (patch or subarray) position into a array.
For example:
array=(9 5 8 3 2 7 5 9 0 1 1 5 4 3 8 9 6 2 6 5 7 9 8);
patch=(0 1 1 5)
I would like to obtain a output equals to 8 (start position of my patch in relation to array) or 11 (final position).
bash doesn't really have any built-in facility to do this; you need to walk the array yourself:
for ((i=0; i<${#array[#]}; i++)); do
for ((j=0; j<${#patch[#]}; j++)); do
# Make sure the corresponding elements
# match, or give up. RHS is quoted to ensure
# actual string equality, rather than just pattern matching
[[ ${array[i+j] == "${patch[j]}" ]] || break
done
if [[ $j == ${#patch[#] ]]; then
# All the comparisons succeeded!
start=$i
finish=$((i+j-1))
break
fi
done

bad substitution shell- trying to use variable as name of array

#!/bin/bash
array=( 2 4 5 8 15 )
a_2=( 2 4 8 10 )
a_4=( 2 4 8 10 )
a_5=( 10 12 )
a_8=( 8 12 )
a_15=( 2 4 )
numberOfTests=5
while [ $i -lt ${#array[#]} ]; do
j=0
currentArray =${array[$i]}
*while [ $j -lt ${#a_$currentArray [#]} ]; do #### this line i get ->>>> bad substitution*
./test1.sh "${array[$i]}" -c "${a_"$currentArray "[$j]}" &
let j=j+1
done
let i=i+1
done
so Im trying this code, loop over an array(called array), The array should point out
the array number we are now looping(a_X). And every time to point out the current place and value.
can anybody help me how im using the $currentArray to work properly so I can know the length of the array and the value?
I get in the line I marked an error.
Thank you guys!
The simplest solution is to store the full names of the arrays, not just the numerical suffix, in array. Then you can use indirect parameter expansion while iterating directly over the values, not the indices, of the arrays.
# Omitting numberOfTests has it does not seem to be used
array=(a_2 a_4 a_5 a_8 a_15)
a_2=( 2 4 8 10 )
a_4=( 2 4 8 10 )
a_5=( 10 12 )
a_8=( 8 12 )
a_15=( 2 4 )
for arr in "${array[#]}"; do
currentArray=$arr[#]
for value in "${!currentArray}"; do
./test1.h "${arr#a_}" -c "$value" &
done
done

Sorting an array in a BASH Script by columns and rows while keeping them intact

I have a test file that looks like this:
1 1 1 1 1
9 3 4 5 5
6 7 8 9 7
3 6 8 9 1
3 4 2 1 4
6 4 4 7 7
Each row is supposed to represent a students grades. So the user puts in either an 'r' or a 'c' into the command line to choose to sort by rows or columns, followed by the file name. Sorting by rows would represent getting a students average and sorting my columns would represent a particular assignments average.
I am not doing anything with the choice variable yet because I need to get the array sorted first so I can take the averages and then get the median for each column and row.
So im not sure how I can choose to sort by those specific options. Here is what I have so far:
#!/bin/bash
choice="$1"
filename="$2"
c=0
if [ -e "$filename" ]
then
while read line
do
myArray[$c]=$line
c=$(expr $c + 1)
done < "$filename"
else
echo "File does not exist"
fi
printf -- '%s\n' "${myArray[#]}"
FS=$'\n' sorted=($(sort -n -k 1,1<<<"${myArray[*]}"))
echo " "
printf '%s\n' "${sorted[#]}"
This is only sorting the first column though and im not sure why its even doing that. Any push in the right direction would be appreciated. Examples would help a ton, thanks!
UPDATE:
With the changes that were suggested I have this so far:
#!/bin/sh
IFS=$'\n';
choice="$1"
filename="$2"
if [ -e "$filename" ]
then
while read line
do
myArray[$c]=$line
c=$(expr $c + 1)
done < "$filename"
else
echo "File does not exist."
fi
printf -- '%s\n' "${myArray[#]}"
width=${myArray[0]// /}
width=${#width}
height=${#myArray[#]}
bar=()
for w in $(seq 0 1 $((${width}-1)))
do
tmp=($(sort -n <<<"${myArray[*]}"))
for h in $(seq 0 1 $((${height}-1)))
do
myArray[h]=${myArray[h]#* }
bar[h]="${bar[h]} ${tmp[h]%% *}"
bar[h]="${bar[h]# }"
done
done
printf -- '%s\n' "${bar[*]}"
But now I am getting some really strange output of way more numbers than i started with and in a seemingly random order.
actually it is sorting $line(s) which are strings. you need to initialize the column to sort correctly, so that it is an array
UPDATE:
the following code is really straight forward. no performance aspects are regarded. so for large datasets this will take a while to sort column wise. your datasets have to contain lines of numbers seperated by single spaces to make this work.
#!/bin/bash
IFS=$'\n';
# here you can place your read line function
ar[0]="5 3 2 8"
ar[1]="1 1 1 1"
ar[2]="3 2 4 5"
printf -- '%s\n' "${ar[*]}" # print the column wise unsorted ar
echo
# sorting
width=${ar[0]// /}
width=${#width}
height=${#ar[#]}
bar=()
for w in $(seq 0 1 $((${width}-1))); do # for each column
#sort -n <<<"${ar[*]}" # debug, see first column removal
tmp=($(sort -n <<<"${ar[*]}")) # this just sorts lexigraphically by "first column"
# rows are strings, see initial definition of ar
#echo
for h in $(seq 0 1 $((${height}-1))); do # update first column
ar[h]=${ar[h]#* } # strip first column
bar[h]="${bar[h]} ${tmp[h]%% *}" # add sorted column to new array
bar[h]="${bar[h]# }" # trim leading space
done
#printf -- '%s\n' "${bar[*]}" # debug, see growing bar
#echo "---"
done
printf -- '%s\n' "${bar[*]}" # print the column wise sorted ar
prints out the unsorted and sorted array
5 3 2 8
1 1 1 1
3 2 4 5
1 1 1 1
3 2 2 5
5 3 4 8

How to slice an array in Bash

Looking the "Array" section in the bash(1) man page, I didn't find a way to slice an array.
So I came up with this overly complicated function:
#!/bin/bash
# #brief: slice a bash array
# #arg1: output-name
# #arg2: input-name
# #args: seq args
# ----------------------------------------------
function slice() {
local output=$1
local input=$2
shift 2
local indexes=$(seq $*)
local -i i
local tmp=$(for i in $indexes
do echo "$(eval echo \"\${$input[$i]}\")"
done)
local IFS=$'\n'
eval $output="( \$tmp )"
}
Used like this:
$ A=( foo bar "a b c" 42 )
$ slice B A 1 2
$ echo "${B[0]}" # bar
$ echo "${B[1]}" # a b c
Is there a better way to do this?
See the Parameter Expansion section in the Bash man page. A[#] returns the contents of the array, :1:2 takes a slice of length 2, starting at index 1.
A=( foo bar "a b c" 42 )
B=("${A[#]:1:2}")
C=("${A[#]:1}") # slice to the end of the array
echo "${B[#]}" # bar a b c
echo "${B[1]}" # a b c
echo "${C[#]}" # bar a b c 42
echo "${C[#]: -2:2}" # a b c 42 # The space before the - is necesssary
Note that the fact that a b c is one array element (and that it contains an extra space) is preserved.
There is also a convenient shortcut to get all elements of the array starting with specified index. For example "${A[#]:1}" would be the "tail" of the array, that is the array without its first element.
version=4.7.1
A=( ${version//\./ } )
echo "${A[#]}" # 4 7 1
B=( "${A[#]:1}" )
echo "${B[#]}" # 7 1
Array slicing like in Python (From the rebash library):
array_slice() {
local __doc__='
Returns a slice of an array (similar to Python).
From the Python documentation:
One way to remember how slices work is to think of the indices as pointing
between elements, with the left edge of the first character numbered 0.
Then the right edge of the last element of an array of length n has
index n, for example:
```
+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 |
+---+---+---+---+---+---+
0 1 2 3 4 5 6
-6 -5 -4 -3 -2 -1
```
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 1:-2 "${a[#]}")
1 2 3
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 0:1 "${a[#]}")
0
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice 1:1 "${a[#]}")" ] && echo empty
empty
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice 2:1 "${a[#]}")" ] && echo empty
empty
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice -2:-3 "${a[#]}")" ] && echo empty
empty
>>> [ -z "$(array.slice -2:-2 "${a[#]}")" ] && echo empty
empty
Slice indices have useful defaults; an omitted first index defaults to
zero, an omitted second index defaults to the size of the string being
sliced.
>>> local a=(0 1 2 3 4 5)
>>> # from the beginning to position 2 (excluded)
>>> echo $(array.slice 0:2 "${a[#]}")
>>> echo $(array.slice :2 "${a[#]}")
0 1
0 1
>>> local a=(0 1 2 3 4 5)
>>> # from position 3 (included) to the end
>>> echo $(array.slice 3:"${#a[#]}" "${a[#]}")
>>> echo $(array.slice 3: "${a[#]}")
3 4 5
3 4 5
>>> local a=(0 1 2 3 4 5)
>>> # from the second-last (included) to the end
>>> echo $(array.slice -2:"${#a[#]}" "${a[#]}")
>>> echo $(array.slice -2: "${a[#]}")
4 5
4 5
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -4:-2 "${a[#]}")
2 3
If no range is given, it works like normal array indices.
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -1 "${a[#]}")
5
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -2 "${a[#]}")
4
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 0 "${a[#]}")
0
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 1 "${a[#]}")
1
>>> local a=(0 1 2 3 4 5)
>>> array.slice 6 "${a[#]}"; echo $?
1
>>> local a=(0 1 2 3 4 5)
>>> array.slice -7 "${a[#]}"; echo $?
1
'
local start end array_length length
if [[ $1 == *:* ]]; then
IFS=":"; read -r start end <<<"$1"
shift
array_length="$#"
# defaults
[ -z "$end" ] && end=$array_length
[ -z "$start" ] && start=0
(( start < 0 )) && let "start=(( array_length + start ))"
(( end < 0 )) && let "end=(( array_length + end ))"
else
start="$1"
shift
array_length="$#"
(( start < 0 )) && let "start=(( array_length + start ))"
let "end=(( start + 1 ))"
fi
let "length=(( end - start ))"
(( start < 0 )) && return 1
# check bounds
(( length < 0 )) && return 1
(( start < 0 )) && return 1
(( start >= array_length )) && return 1
# parameters start with $1, so add 1 to $start
let "start=(( start + 1 ))"
echo "${#: $start:$length}"
}
alias array.slice="array_slice"
At the risk of beating a dead horse, I was inspired by #jandob's answer and made this version that
Is simpler (doesn't have so much shift logic or rewriting of variables as often).
Respects quoted strings without dealing with IFS (-r mode only).
Allows the user to specify [start, end) slicing or [start, length] slicing via -l flag.
Allows you to echo the resulting array (default behavior), or "return" it into a new array for use in the calling parent (via -r slicedArray).
Note: namerefs are only supported in Bash >= 4.3. To support earlier versions of Bash (i.e. Mac without Brew's bash), you'll need to use indirection instead: use a temp var to access array parameters, e.g. declare arrValuesCmd="$1[#]"; declare arr=("${!arrValuesCmd}"), and use eval for return values, e.g. eval $retArrName='("${newArr[#]}")' (note the single quotes around the array declaration).
array.slice() {
# array.slice [-l] [-r returnArrayName] myArray 3 5
# Default functionality is to use second number as end index for slice (exclusive).
# Can instead use second number as length by passing `-l` flag.
# `echo` doesn't maintain quoted entries, so pass in `-r returnArrayName` to keep them.
declare isLength
declare retArrName
declare OPTIND=1
while getopts "lr:" opt; do
case "$opt" in
l)
# If `end` is slice length instead of end index
isLength=true
;;
r)
retArrName="$OPTARG"
;;
esac
done
shift $(( OPTIND - 1 ))
declare -n arr="$1"
declare start="$2"
declare end="$3"
declare arrLength="${#arr[#]}"
declare newArr=()
declare newArrLength
# Bash native slicing:
# Positive index values: ${array:start:length}
# Negative index values: ${array: start: length}
# To use negative values, a space is required between `:` and the variable
# because `${var:-3}` actually represents a default value,
# e.g. `myVar=${otherVal:-7}` represents (pseudo-code) `myVar=otherVal || myVar=7`
if [[ -z "$end" ]]; then
# If no end is specified (regardless of `-l`/length or index), default to the rest of the array
newArrLength="$arrLength"
elif [[ -n "$isLength" ]]; then
# If specifying length instead of end-index, use native bash array slicing
newArrLength="$(( end ))"
else
# If specifying end-index, use custom slicing based on a range of [start, end):
newArrLength="$(( end - start ))"
fi
newArr=("${arr[#]: start: newArrLength}")
if [[ -n "$retArrName" ]]; then
declare -n retArr="$retArrName"
retArr=("${newArr[#]}")
else
echo "${newArr[#]}"
fi
}
Examples:
myArray=(x y 'a b c' z 5 14) # length=6
array.slice myArray 2 4
# > a b c z
array.slice -l myArray 3 2
# > z 5
# Note: Output was manually quoted to show the result more clearly.
# Actual stdout content won't contain those quotes, which is
# why the `-r returnArray` option was added.
array.slice -r slicedArray myArray -5 -3 # equivalent of [2, 4)
# > (null)
echo -e "myArray (length=${#myArray[#]}): ${myArray[#]} \nslicedArray (length=${#slicedArray[#]}): ${slicedArray[#]}"
# > myArray (length=6): x y 'a b c' z 5 14
# > slicedArray (length=2): 'a b c' z
array.slice -lr slicedArray myArray -5 3 # length instead of index, equivalent of [2, 5)
# > (null)
echo -e "myArray (length=${#myArray[#]}): ${myArray[#]} \nslicedArray (length=${#slicedArray[#]}): ${slicedArray[#]}"
# > myArray (length=6): x y 'a b c' z 5 14
# > slicedArray (length=3): 'a b c' z 5

Resources