add entries to an array in bash - linux

In bash I'm trying to create an array and then run through a loop a number of times (determined by the user of the file) and then add a choice to that array the predetermined number of times. This is trade data, so for example, I choose 2 for factors. Then the program asks me to input the factor I want, and I put in open (open price of the day), then bid is added to the array arr and the question is asked again. Then I put in close (close price of the day) then close is added to the array, and in the end the
arr = open close like that. But I run the code and the question: "How many factors would you like to check total: " simply runs over and over again and I never leave the loop and it never appears that the inputs are being put into the array. Any help as to my mistake here is greatly appreciated. Thanks.
factor=""
total=0
declare -a arr
read -p "How many factors would you like to check total: " -e -i "$total" total
for (( x=1; x=total; x++ ))
do
read -p "Enter factor from list: " -e -i "$factor" factor
arr+=(${arr[#]} "$factor")
done
echo ${arr[#]}

You almost got it correct on array append. Just remember that += operator doesn't need full reference to array again on RHS. e.g. just
arr+=($factor)
Would be suffice to append $factor at the end of array variable arr.
Modify your script a little bit like this:
factor=""
total=0
declare -a arr
read -p "How many factors would you like to check total: " -e -i "$total" total
for (( x=1; x<=total; x++ ))
do
read -p "Enter factor from list: " -e -i "$factor" factor
arr+=($factor)
done
echo ${arr[#]}

You have a typo
for (( x=1; x=total; x++ ))
should be
for (( x=1; x==total; x++ ))
In the first one you are assigning total to x which is always true. In the second one you are checking for equality.

Related

how can i make the lines variable in a file? [duplicate]

I'm trying to read from a file, that has multiple lines, each with 3 informations I want to assign to the variables and work with.
I figured out, how to simply display them each on the terminal, but can't figure out how to actually assign them to variables.
while read i
do
for j in $i
do
echo $j
done
done < ./test.txt
test.txt:
1 2 3
a b c
So I want to read the line in the outer loop, then assign the 3 variables and then work with them, before going to the next line.
I'm guessing I have to read the values of the lines without an inside loop, but I can't figure it out right now.
Hope someone can point me in the right direction.
I think all you're looking for is to read multiple variables per line: the read command can assign words to variables by itself.
while read -r first second third; do
do_stuff_with "$first"
do_stuff_with "$second"
do_stuff_with "$third"
done < ./test.txt
The below assumes that your desired result is the set of assignments a=1, b=2, and c=3, taking the values from the first line and the keys from the second.
The easy way to do this is to read your keys and values into two separate arrays. Then you can iterate only once, referring to the items at each position within those arrays.
#!/usr/bin/env bash
case $BASH_VERSION in
''|[123].*) echo "ERROR: This script requires bash 4.0 or newer" >&2; exit 1;;
esac
input_file=${1:-test.txt}
# create an associative array in which to store your variables read from a file
declare -A vars=( )
{
read -r -a vals # read first line into array "vals"
read -r -a keys # read second line into array "keys"
for idx in "${!keys[#]}"; do # iterate over array indexes (starting at 0)
key=${keys[$idx]} # extract key at that index
val=${vals[$idx]} # extract value at that index
vars[$key]=$val # assign the value to the key inside the associative array
done
} < "$input_file"
# print for debugging
declare -p vars >&2
echo "Value of variable a is ${vars[a]}"
See:
BashFAQ #6 - How can I use variable variables (indirect variables, pointers, references) or associative arrays?
The bash-hackers page on the read builtin, documenting use of -a to read words into an array.

Create sequence from literal variable inputs [duplicate]

This question already has answers here:
Brace expansion with variable? [duplicate]
(6 answers)
Closed 1 year ago.
I'm trying to create sequences as follows:
startDay=1
endDay=2
dayRange="{$startDay..$endDay}"
echo \[\"$dayRange\",\"{00..02}\"\]
The output is:
["{1..2}","00"] ["{1..2}","01"] ["{1..2}","02"]
When specifying the sequence directly {00..02}, it auto creates "00", "01", "02", but it does not understand the dayRange variable.
What I expect it to return is:
["1","00"] ["1","01"] ["1","02"] ["2","00"] ["2","01"] ["2","02"]
Not sure what I missed.
Please advise.
First idea would be a simple nested for loop:
startDay=1
endDay=2
pfx=
out=
for ((i=startDay; i<=endDay; i++))
do
for j in {00..02}
do
out+="${pfx}[\"${i}\",\"${j}\"]"
pfx=" "
done
done
echo "${out}"
This generates:
["1","00"] ["1","01"] ["1","02"] ["2","00"] ["2","01"] ["2","02"]
A bit less coding, and a bit faster, which uses OP's echo ... {00..02} to eliminate one of the for loops:
NOTE: this eliminates the subprocess $(echo ...) call I had in a previous edit.
startDay=1
endDay=2
for ((i=startDay; i<=endDay; i++))
do
echo -n "[\""${i}"\",\""{00..02}"\"]"
echo -n " "
done
echo ""
This also generates:
["1","00"] ["1","01"] ["1","02"] ["2","00"] ["2","01"] ["2","02"]
Here's one awk idea:
awk -v start=$"${startDay}" -v end="${endDay}" '
BEGIN {
pfx=""
out=""
for (i=start; i<=end; i++)
for (j=0; j<=2; j++) {
out=out pfx "[\"" i "\",\"" sprintf("%02d", j) "\"]"
pfx=" "
}
print out
}'
This also generates:
["1","00"] ["1","01"] ["1","02"] ["2","00"] ["2","01"] ["2","02"]
With the elimination of the earlier subprocess $(echo ...) the first 2 solutions come in with single-digit millisecond timings while the awk solution comes in with low double-digit millisecond timings.
As the number of days (and/or sequence size) increases the first 2 solutions start taking longer (the nested for loop falling farther behind) while the awk solution tends to maintain the same speed.
For really large increases (number of days and/or sequence size) I would expect awk to close in on, and eventually take, the lead.

Why do I get this strange output with an associative array in bash when I perform a manual bubble sort?

I have a bash associative array that looks like this
declare -A arraySalary=( [1]=1000 [8]=3000 [2]=2000)
I am learning bash scripting and am trying to implement a bubble sort on the array
with this piece of code
sortedDesc=false
while ! $sortedDesc ;
do
sortedDesc=true
for ((currentIndex=0; currentIndex<$((${#arraySalary[#]} -1)); currentIndex++))
do
if [[ ${arraySalary[$((currentIndex))]} -lt ${arraySalary[$((currentIndex + 1))]} ]]
then
sortedDesc=false
biggerNumber=${arraySalary[((currentIndex - 1))]}
arraySalary[$((currentIndex + 1))]=${arraySalary[$((currentIndex))]}
arraySalary[currentIndex]=${biggerNumber}
echo "swapped"
fi
done
done
echo "Printing new values"
# Print new values
for key in "${!arraySalary[#]}";
do
echo $key "->" ${arraySalary[$key]}
done
but the output I get is
swapped
swapped
Printing new values
0 -> 2000
1 -> 1000
2 ->
Can someone please explain why this is? Thanks
I don't know why you have chosen to use an associative array instead of an ordinary array, but you need to be aware that the index of an associative array is not a numeric context.
If arraySalary had been declared with -a instead of -A, then this would have been fine:
arraySalary[currentIndex]=${biggerNumber}
because the index of an ordinaey array is a numeric context, and in numeric contexts you can use variabke names without $. But since it is associative, what that does is set the element whose key is the string currentIndex. What you want is:
arraySalary[$currentIndex]=${biggerNumber}
It is not necessary to write $((currentIndex)); bash does not distinguish between integers and strings which contain the integer.
Sorting an array just by its value will be easy. In the case that you sort keys
by its value by using an assoc array, you need to introduce another array to hold the order of the elements because an assoc array is orderless.
Let's name the new array index which holds (1 8 2 ..) then the script will look like:
#!/bin/bash
declare -A arraySalary=([1]=1000 [8]=3000 [2]=2000)
declare -a index=("${!arraySalary[#]}")
sortedDesc=false
while ! $sortedDesc ;
do
sortedDesc=true
for ((i=0; i<$((${#index[#]} - 1)); i++))
do
if [[ ${arraySalary[${index[$i]}]} -lt ${arraySalary[${index[$(($i + 1))]}]} ]]
then
sortedDesc=false
biggerIndex=${index[$(($i + 1))]}
index[$(($i + 1))]=${index[$i]}
index[$i]=$biggerIndex
echo "swapped"
fi
done
done
echo "Printing new values"
# Print new values
for key in "${index[#]}";
do
echo $key "->" ${arraySalary[$key]}
done
which yields
swapped
swapped
swapped
Printing new values
8 -> 3000
2 -> 2000
1 -> 1000

Bash initialize sparse array

I have an array, index is the hard drive size and value is number of hard drives having same size. So this is what i do.
DRIVE_SIZES[$DRIVE_SIZE]=`expr ${DRIVE_SIZES[$DRIVE_SIZE]} + 1`
I have not initialized the DRIVE_SIZES array to 0. So above line might not work.
I would like to initialize a sparse array in bash script.
Lets say all drives in the host are of the same size, except one. Some 10 drives are of size 468851544 and one drive is of size 268851544. So I cannot initialize all index from 0-468851544 because I dont know the maximum disk size beforehand.
So is there a way to initialize such an sparse array to 0. May be if there is way to declare an integer array in bash that might help. But after some initial research found out I can declare an integer, but not integer array(might be wrong on this). Can someone help me with this ?
I read this, but this might not be the solution to me
Use increment in an arithmetic expression:
(( ++DRIVE_SIZES[DRIVE_SIZE] ))
You can use parameter substitution to put a zero into the expression when the array key has not been defined yet:
DRIVE_SIZES[$DRIVE_SIZE]=`expr ${DRIVE_SIZES[$DRIVE_SIZE]:-0} + 1`
N.B. this is untested, but should be possible.
An array element when unset and used in arithmetic expressions inside (( )) and $(( )) has a default value of 0 so an expression like this would work:
(( DRIVE_SIZES[DRIVE_SIZE] = DRIVE_SIZES[DRIVE_SIZE] + 1 ))
Or
(( DRIVE_SIZES[DRIVE_SIZE] += 1 ))
Or
(( ++DRIVE_SIZES[DRIVE_SIZE] ))
However when used outside (( )) or $(( )), it would still expand to an empty message:
echo ${DRIVE_SIZES[RANDOM]} shows "" if RANDOM turns out to be an index of an element that is unset.
You can however use $(( )) always to get the proper presentation:
echo "$(( DRIVE_SIZES[RANDOM] ))" would return either 0 or the value of an existing element, but not an empty string.
Using -i to declare, typeset or local when declaring arrays or simple variables might also help since it would only allow those parameters to have integral values, and the assignment is always done as if it's being assigned inside (( )) or $(( )).
declare -i VAR
A='1 + 2'
is the same as (( A = 1 + 2 )).
And with (( B = 1 )), A='B + 1' sets A to 2.
For arrays, you can set them as integral types with -a:
declare -a -i ARRAYVAR
So a solution is to just use an empty array variable and that would be enough:
ARRAYVAR=()
Or
declare -a -i ARRAYVAR=()
Just make sure you always use it inside (( )) or $(( )).

stop a reading when read = 0

I'm trying to do something like these:
while[$read!="0"];
In this program
#!/bin/sh
i=0
cont=0
while[$read!="0"]; do
read number
cont=`expr $cont + $number`
i++
done
cont=`expr $cont / $i -1`
echo
I want to stop suming the entries when I give it a 0
tnx
The variable you're reading into is $number, so reference that rather than $read in your loop.
Whitespace is significant, so make sure to include spaces before, after, and between all of the items in your loop. (Confusingly, you must not include spaces in an assignment statement like i=0. i = 0 is wrong.)
For good measure, use double quotes around the variable. That's a good practice so that if the user hits enter without typing a number your script doesn't barf on the empty string.
while [ "$number" != "0" ]; do
Also, your i++ isn't right. There are various ways to write that, the simplest being:
let i++
In this, an infinite loop would be appropriate, since you know the condition on which you want to (hint) break out of the loop. The way to get an infinite loop in sh is: while true; do ...; done
Also, read has a -p option that lets you have a prompt (so you know what you're being asked to enter): read -p "Enter a number: " number

Resources