I want to manage subvariables in Bash. I can assign the subvariables, but I dont know how to use it:
#/bin/bash
n=1
for lvl in 1 2;
do
export key$n="${RANDOM:0:2}"
let n=$n+1
done
for num in 1 2; do
echo $key$num
done
If I use echo $key$num, it print number sequence of variable $num, and not the random numbers
Use arrays.
for n in 1 2; do
key[n]="${RANDOM:0:2}"
done
for num in 1 2; do
echo "${key[num]}"
done
See http://mywiki.wooledge.org/BashGuide/Arrays.
Also, in bash you'll generally do better counting from 0 instead of 1, and you don't need to export variables unless you want to run some other program that is going to look for them in its inherited environment.
You may use arrays (see #MarkReed), or use declare:
for n in 1 2; do
declare -- key$n="${RANDOM:0:2}"
done
for n in 1 2; do
v=$(declare -p key$n) ; v="${v#*=}" ; echo "${v//\"/}"
done
The same using functions:
key_set () # n val
{
declare -g -- key$1=$2
}
key_get () # n
{
local v=$(declare -p key$1) ; v="${v#*=}" ; echo "${v//\"/}"
}
for n in 1 2; do
key_set $n "${RANDOM:0:2}"
done
for n in 1 2; do
key_get $n
done
Related
Working on a little script which put random numbers in a 10 000 size array and then sort all this array with the method ask during the course.
I've done this code but it seem that it begin to sort (when I test I have some "a" that are printed but not as much as supposed to and I don't understand why)
I'm believing the problem come fromes my test on val array, and it's probably a beginner error but I don't really know how to find the problem on th web as I don't really now which line is the problem.
I don't necessary need an answer, just some clues to find it could be good :)
Here is my code: (new to stackoverflow so I don't know how to put a good code view directly, if anyone can show me)
for i in `seq 1 10000`;
do
val[${i}]=$RANDOM
done
echo `date +"%M.%S.%3N"`
FLAG=0
until [ $FLAG -eq 1 ]
do
FLAG=1
for j in `seq 1 9999`;
do
if [ ${val[${j}]} -gt ${val[${j+1}]} ]
then
TMP=${val[${j}]}
val[${j}]=${val[${j+1}]}
val[${j+1}]=$TMP
FLAG=0
echo a
fi
done
done
echo `date +"%M.%S.%3N"`
as asked I can't really have a useful output as I just want the date before and after the sort operation. But the sort is just supposed to put values from lower to higher by taking them two by two and invert them if necessary. Doing this until no numbers are inverted.
Edit: I tried with manual number:
10 3 6 9 1
when running it by putting echo ${val[*]} in the for loop it just print 4 times the same list in the same order, so I'm guessing it doesn't work at all... Is my use of "if" wrong ?
Edit 2: At the begining, I did it in C# and I wanted to do it in shell then, firstly because I wanted to practice shell and then because I wanted to compare efficiency and time needed for the same thing. here is the C# code, working.
Random random = new Random();
int[] _tab = new int[100000];
for (int i = 0; i < _tab.Length; i++)
{
_tab[i] = random.Next(1, _tab.Length);
}
bool perm;
int tmp;
DateTime dt = DateTime.Now;
do
{
perm = false;
for (int i = 0; i < (_tab.Length - 1); i++)
{
if (_tab[i] > _tab[i + 1])
{
tmp = _tab[i];
_tab[i] = _tab[i + 1];
_tab[i + 1] = tmp;
perm = true;
}
}
}
while (perm == true);
Console.WriteLine((DateTime.Now - dt).TotalMilliseconds);
Console.Read();
Thanks :)
If my understanding that you want to know why this script is not producing an "a" indicating the ordering of the array of the numbers initially produced in the "for" loop is correct, then here is a solution:
The syntax is incorrect for your variable expansion. The ${var} cannot have math operators inside the braces, because they have different meaning here. In a normal non-associative array Zsh handles subscripts with some basic math support, so you can use ${array[var+1]} instead of ${array[${var+1}]} as you previously did.
I suspect the reason this came about - complicated, error prone POSIX syntax - would have been avoided by using simplified Zsh syntax, but as stated in an earlier comment, it would not be portable to other shells.
Some shells support similar features: Bash supports most, but not bare subscripts ($array[var]). Strings may be ordered in Zsh in a similar manner, but the math-context brackets (( and )) would have to be replaced with normal test brackets [[ and ]] and the array $val might have to be defined with special typeset options to make the strings compare in the desired manner; that is, they might have to be padded and right or left aligned. For comparing enumeration types, like Jan - Feb, it gets a little more complicated with associative arrays and case-conversion.
Here is the script with the appropriate changes, then again in simplified Zsh:
#!/bin/sh
for i in `seq 1 10000`;
do
val[$((i))]=$RANDOM
done
echo `date +"%M.%S.%3N"`
FLAG=0
until [ $FLAG -eq 1 ]
do
FLAG=1
for j in `seq 1 9999`;
do
if [ ${val[$((j))]} -gt ${val[$((j+1))]} ]
then
TMP=${val[$((j))]}
val[$((j))]=${val[$((j+1))]}
val[$((j+1))]=$TMP
FLAG=0
echo a
fi
done
done
echo `date +"%M.%S.%3N"`
Zsh:
#!/bin/zsh
foreach i ( {1..10000} )
val[i]=$RANDOM
end
echo `date +"%M.%S.%3N"`
FLAG=0
until ((FLAG))
do
FLAG=1
foreach j ( {1..9999} )
if (( val[j] > val[j+1] ))
then
TMP=$val[j]
val[j]=$val[j+1]
val[j+1]=$TMP
FLAG=0
echo a
fi
end
done
echo `date +"%M.%S.%3N"`
Hi :) Need help on a project, working on shell scripting and need to figure out how to print car names after certain numbers when they're divisible by certain numbers in a list.
Here's the generator, it takes two integers from the user, (Section where they're prompted not included), and prints the evens between those. I need to print car names after numbers divisible by: 5, 7, 10.
5 = Ford 7 = Bmw 10 = Rover
Generator:
for ((n = n1; n<= n2; ++n)); do
out=$(( $n % 2 ))
if [ $out -eg 0 ] ; then
echo "$n"
fi
done
Any help would be appreciated :( I'm completely clueless :(
Thanks
awk to the rescue!
$ awk -v start=0 -v end=70 -v pat='5=Ford,7=Bmw,10=Rover'
'BEGIN{n=split(pat,p,",");
for(i=1;i<=n;i++)
{split(p[i],d,"=");
a[d[1]]=d[2]}
for(i=start;i<=end;i+=2)
{printf "%s ",i;
for(k in a)
if(i%k==0)
printf "%s ", a[k];
print ""}}'
instead of hard coding the fizzbuzz values, let the program handle it for you. script parses the pattern and assigns divisor/value to a map a. While iterating over from start to end two by two check for each divisor and if divides append the tag to the line. Assumes start is entered as an even value if not need to guard for that too.
Do you mean something like this?
n1=0
n2=10
for ((n = n1; n <= n2; ++n)); do
if (( n % 2 == 0)); then
echo -n $n
if (( n % 5 == 0)); then
echo -n ' Ford'
fi
if (( n % 7 == 0)); then
echo -n ' BMW'
fi
if (( n % 10 == 0)); then
echo -n ' Rover'
fi
echo
fi
done
Output
0 Ford BMW Rover
2
4
6
8
10 Ford Rover
Not sure you want the 0 line containing names though, but that's how % works. You can add an explicit check for 0 if you wish.
Some of you are probably familiar with Project Euler, and I'm currently attempting a few of their problems to teach myself some more bash. They're a bit more mathematical than 'script-y' but it helps with syntax etc.
The problem currently asks me to solve:
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
The code I have looks like so:
#!/bin/bash
i="1"
for i in `seq 1 333`
do
threes[$i]=`calc $i*3` # where 'calc' is a function written in bashrc
#calc actually looks like: calc() {awk "BEGIN { print "$*"} }
let "sumthrees = sumthrees + ${threes[$i]}"
done
for i in `seq 1 199`
do
fives[$i]=`calc $i*5`
let "sumfives = sumfives + ${fives[$i]}"
done
let "ans = $sumfives + $sumthrees"
echo "The sum of all 3 factors is $sumthrees and the sum of all five factors is $sumfives"
echo "The sum of both is $ans"
#So I can repeatedly run the script without bash remembering the variables between executions
unset i
unset fives
unset threes
unset sumfives
unset sumthrees
unset ans
So far I've not gotten the correct answer, but have run out of ideas as to where I'm going wrong. (FYI, the script currently gives me 266333, which I believe is close, but I don't know the answer yet.)
Can anyone spot anything? And for my own learning, if there are more elegant solutions to this that people might like to share that would be great.
EDIT
Thanks for all the answers, super informative. Since there are so many useful answers here I'll accept my favourite as the proper thread answer.
Blue Moon pointed out the actual problem with your logic.
You don't need to store all the threes and fives in arrays because you don't need them later.
You don't need to unset variables at the end of a script if you use ./yourscript or bash script because they'll disappear along with the
shell instance (better to initialize them first in any case).
You don't need awk to do math, bash does that just fine.
seq and let are not the best way to do anything in a bash script.
Here's a straight forward version:
#!/bin/bash
sum=0
for ((i=1; i<1000; i++))
do
if (( i%3 == 0 || i%5 == 0 ))
then
(( sum += i ))
fi
done
echo "$sum"
Your logic is almost right except that there are numbers which divide by both 3 and 5. So you are adding these numbers twice. Hence, you get wrong answer.
Use another loop similar to ones you have and subtract the ones that divide by both 3 and 5 from the result.
A few tips you might find useful:
In bash, you use let to give the shell a hint that a variable should be considered a number. All bash variables are strings, but you can do arithmetic on numerical strings. If I say let i=1 then i is set to 1, but if I say let i="taco" then $i will be 0, because it couldn't be read as a number. You can achieve a small amount of type-safety when doing mathematical work in the shell.
Bash also has $((this)) mechanism for doing math! You can check it out yourself: echo $((2 + 2)) -> 4, and even more relevant to this problem: echo $((6 % 3 == 0)) -> 1
In case you aren't familiar, % divides the first number by the second, and gives back the remainder; when the remainder is 0, it means that the first is divisible by the second! == is a test to see if two things are equal, and for logical tests like this 1 represents true and 0 represents false. So I'm testing if 6 is divisible by 3, which it is, and the value I get back is 1.
The test brackets, [ ... ] have a "test for equality" flag, -eq, which you can use to check if a math expression has a certain value (man test for more details)!
$ let i=6
$ echo $((i % 3 == 0 || i % 5 == 0))
1
$ if [ $((i % 3 == 0 || i % 5 == 0)) -eq 1 ]; then echo "yes"; fi
yes
(|| is another logical test - $((a || b)) will be 1 (true) when a is true or b is true).
Finally, instead of doing this for the number 6, you could do it in a for loop and increment a sum variable every time you find a multiple of 3 or 5:
let sum=0
for i in {1..1000}; do
if [ $((i % 3 == 0 || i % 5 == 0)) -eq 1 ]; then
let sum=$((sum + i))
fi
done
echo $sum
And there you'd have a working solution!
Bash has a lot of nice little tricks, (and a lot more mean ugly tricks), but it's worth learning at least a handful of them to make use of it as a scripting tool.
How about creative use of the modulus function & some checks. Then you have just 1 loop.
#!/bin/bash
i=1
while [ $i -lt 1000 ]
do
if [ $(($i % 3)) -eq 0 ] || [ $(($i % 5)) -eq 0 ]
then
sumall=$(($sumall+$i))
fi
i=$(($i+1))
done
echo "The sum of both is $sumall"
Answer: 233168
A different solution:
#!/bin/bash
sum=0
for n in {1..999}; do [ $(((n%5) * (n%3))) -eq 0 ] && sum=$((sum+n)); done
echo $sum
The script loops through all numbers below 1000, tests if the product of the number mod 3 and the number mod 5 is 0 (the product of two numbers can only be zero if one of them is zero). If that is the case, it adds the current number to a sum, which is printed out afterwards.
By the way, if I were you I'd include the definition of the calc function inside the script, to get a self-contained solution that doesn't need your specific configuration.
I have a CSV file, and I wanna remove the columns that have less than 5 different values. e.g
a b c;
1 1 1;
1 2 2;
1 3 4;
2 4 5;
1 6 7;
then I wanna remove column a since it has only two different values (1,2). How to do this?
A solution using arrays:
infile="infile.txt"
different=5
rows=0
while read -a line ; do
data+=( ${line[#]/;/} ) # remove all semicolons
((rows++))
done < "$infile"
cols=$(( ${#data[#]}/rows )) # calculate number of rows
result=()
for (( CNTR1=0; CNTR1<cols; CNTR1+=1 )); do
cnt=()
save=( ${data[CNTR1]} ) # add column header
for (( CNTR2=cols; CNTR2<${#data[#]}; CNTR2+=cols )); do
cnt[${data[CNTR1+CNTR2]}]=1
save+=( ${data[CNTR1+CNTR2]} ) # add column data
done
if [ ${#cnt[#]} -eq $different ] ; then # choose column?
result+=( ${save[#]} ) # add column to the result
fi
done
cols=$((${#result[#]}/rows)) # recalculate number of columns
for (( CNTR1=0; CNTR1<rows; CNTR1+=1 )); do
for (( CNTR2=0; CNTR2<${#result[#]}; CNTR2+=rows )); do
printf " %s" "${result[CNTR1+CNTR2]}"
done
printf ";\n"
done
The output:
b c;
1 1;
2 2;
3 4;
4 5;
6 7;
I think to resolve this problem you can read this file to get data (numbers) (can put in a array) then search columns you want to remove and write this result back to file at last.
input from file $2 : 1 -> 2
while read -a line; do
if (( ${line[2]} > linesNumber )); then
echo "Graph does not match known sites4"
exit
fi
done < "$2"
For some reason inside the if condition, the value of ${line[2]) is not 2
but if I print the value outside if:
echo `${line[2]}`
2
What's linesNumber? Even if you put $linesNumber, where is it coming from?
If you are tracking the line number, you need to set it and increment it. Here's my sample program and data. It's inspired by your example, but doesn't do exactly what you want. However, it shows you how to setup a variable that tracks the line number, how to increment it, and how to use it in an if statement:
foo.txt:
this 1
that 2
foo 4
barf 4
flux 5
The Program:
lineNum=0
while read -a line
do
((lineNum++))
if (( ${line[1]} > $lineNum ))
then
echo "Line Number Too High!"
fi
echo "Verb = ${line[0]} Number = ${line[1]}"
done < foo.txt
Output:
Verb = this Number = 1
Verb = that Number = 2
Line Number Too High!
Verb = foo Number = 4
Verb = barf Number = 4
Verb = flux Number = 5