How to combine a string and array - arrays

I have an array of numbers 0 10 20 30 40 and I am trying to add a string to the front the array so that the output will look like this: 1D: 0 10 20 30 40
I've tried coding it this way:
string="1D: "
new=( $(( $string + ${array[#]} )) )
echo $new
Which gives me this error:
-bash: 1D: value too great for base (error token is "1D")
Can someone assist me in this problem?
Thanks!

You can use:
array=(0 10 20 30 40)
string="1D:"
new=("$string" "${array[#]}")
Check new array:
declare -p new
declare -a new='([0]="1D:" [1]="0" [2]="10" [3]="20" [4]="30" [5]="40")'
Or else:
echo "${new[#]}"
1D: 0 10 20 30 40

If you need to prepend a string to an array you can do something like this
array=("0" "10" "20" "30" "40")
array=("1D:" "${array[#]}")
echo ${array[#]}
output will be
1D: 0 10 20 30 40

Related

How to increment through the values in a list?

I have repeatedly tried to search for this question, but cannot form a search that produces results that are actually relevant to my question.
I am trying to build a script that parses an SVG file (XML text format file that produces graphic content) looking for specific cues and assigns RGB values. (Each RGB value will be slightly different.)
To do this, I picture multiple incrementing variables (for instance $i, $j & $k) that increment based on triggers found while parsing the text file.
However, the amounts that I need to increment are not "1". Also, the values needed are hexadecimal in form.
I could set up something where the vars are incremented by a given amount, such as +33, but I would also need to convert numbers to hex, figure out how to start over, etc.
A far more versatile, powerful and elegant approach occurs to me, but I don't know how to go about it.
How can I set up these incrementing variables to increment through the values I've set up in an array?
For example, say my RGB potential values are #rgbval = (00,33,66,99,cc,ff). How can I make $i go from one value in this list to the next?
Even better, how could I make $i++ (or something similar) mean "go to the next element's value in #rgbval"?
And assuming this is possible, how would I tell Perl to start over at element [0] after reaching the end of the array?
So you have a string that's the hex representation of a number
my $hex = 'cc';
To do arithmetic on it, you first need to convert that to a number.
my $num = hex($hex);
Now we can do arithmetic on it.
$num += 33;
If we want to convert it back to hex, we can use
$hex = sprintf("%02x", $num);
$i is usually used for indexes. If that's what you want, you can use the following:
for my $i (0..$#rgbval) {
# Do something with $i and/or $rgbval[$i]...
}
If instead you want $i to take on each value, you can use the following:
for my $i (#rgbval) {
# Do something with $i...
}
But it seems to be you want a counter that wraps around.
The straightforward solution would be use an if statement.
my $i = 0;
while (...) {
# Do something with $i and/or $rgbval[$i]...
++$i;
$i = 0 if $i == #rgbval;
}
But I'd use modulo arithmetic.
my $i = 0;
while (...) {
# Do something with $i and/or $rgbval[$i]...
$i = ( $i + 1 ) % #rgbval;
}
Alternatively, you could rotate the array.
while (...) {
# Do something with $rgbval[0]...
push #rgbval, shift(#rgbval);
}
Ikegami, what an excellent bunch of information your response held.
I found three of your proposals too appealing to ignore, and tried to understand them. Your first section was about processing the math described, converting into and out of hex.
I tried to wrestle these steps into a "test of concept" script, along with your suggestion of a modulo reset. (Okay, a "test of understanding of concept".)
For the test script I used an iterator rather than searching for a triggering event, that seemed simpler.
The goal was to have the values increment through the hex numbers listed in the example array, and to start over after the last value.
So I iterated up to ten times, to give the values a chance to start over. After I figured out that I needed to add 51 each time instead of 33 to get those example values, and also had to make the numerical value of my array 51 times larger since I was incrementing by 51, it worked pretty well:
my $num = hex("00");
my #rgbval = qw(a b c d e f);
for my $i (0..10) {
print ( "For i=$i, \$num is " , sprintf("%02x ", $num) , "\n");
$num = ( $num + 51 ) % ( 51 * #rgbval );
}
output:
~\Perlscripts>iterate2.pl
For i=0, $num is 00 For i=1, $num is 33 For i=2, $num is 66 For i=3,
$num is 99 For i=4, $num is cc For i=5, $num is ff For i=6, $num is 00
For i=7, $num is 33 For i=8, $num is 66 For i=9, $num is 99 For i=10,
$num is cc
As far as the non-mathy approach, incrementing through the strings of the array, I understood you to be saying I would need to increment the indices that reference the array values. I did manage to confuse my self with the different iterators and what they were doing, but after a few stumbles, I was able to make this approach work as well:
my #hue = qw(00 33 66 99 cc ff);
my $v = 0;
for my $i (0..10) {
print "\$i=$i, \$hue[$v]=" , $hue[$v] , "\n";
$v = ( $v + 1 ) % #hue;
}
output:
~\Perlscripts>iterate2.pl
$i=0, $hue[0]=00 $i=1, $hue[1]=33 $i=2, $hue[2]=66 $i=3,
$hue[3]=99 $i=4, $hue[4]=cc $i=5, $hue[5]=ff $i=6,
$hue[0]=00 $i=7, $hue[1]=33 $i=8, $hue[2]=66 $i=9,
$hue[3]=99 $i=10, $hue[4]=cc
Your last proposed solution, rotating the array with push and shift seemed perhaps the most novel approach and quite compelling, especially once I realized that if i have a variable that stores the shifted value, that will be the correct value to push next time, around and around.
In this approach I don't even have to worry about starting over after the last value; the changing array takes care of that automatically for me:
my #hue = qw(00 33 66 99 cc ff);
for my $i (0..10) {
my $curval = shift(#hue);
print "\$i=$i, \$curval is $curval \.\.\.And the array is currently: ( #hue )\n";
push(#hue,$curval);
}
output:
~\Perlscripts>iterate2.pl
$i=0, $curval is 00 ...And the array is currently: (
33 66 99 cc ff ) $i=1, $curval is 33 ...And the
array is currently: ( 66 99 cc ff 00 ) $i=2, $curval is 66 ...And the array is currently: ( 99 cc ff 00 33 ) $i=3, $curval is
99 ...And the array is currently: ( cc ff 00 33 66 )
$i=4, $curval is cc ...And the array is currently: (
ff 00 33 66 99 ) $i=5, $curval is ff ...And the
array is currently: ( 00 33 66 99 cc ) $i=6, $curval is 00 ...And the array is currently: ( 33 66 99 cc ff ) $i=7, $curval is
33 ...And the array is currently: ( 66 99 cc ff 00 )
$i=8, $curval is 66 ...And the array is currently: (
99 cc ff 00 33 ) $i=9, $curval is 99 ...And the
array is currently: ( cc ff 00 33 66 ) $i=10, $curval is cc ...And the array is currently: ( ff 00 33 66 99 )
Most educational and helpful! Thanks so much.

Two arrays with different length in a loop

I have two arrays with different length, and I need to use them in the same loop.
This is the code
#!/bin/bash
data=`date +%Y-%m-%d`
data1=`date -d "1 day" +%Y-%m-%d`
cd /home/test/em_real/
#first array (today and tomorrow)
days="$data $data1"
#second array (00 till 23)
hours="00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23"
for value in $hours
do
cp /home/test/em_real/mps_"${days[i++]}"_"$value":00:00 /home/DOMAINS/test_case/
sleep 10
done
Tt fails, doesn't get days.
How can I do it?
#fedorqui If now, inside the bucle, I want to remove the dash (-) of days and do another order, I don't know why it doesn't get the string , the code is the following:
days=("$data" "$data1") #create an array properly
for value in {00..23}; do
for day in "${days[#]}"; do
cp "/path/mps_${day}_${value}:00:00" /another/path/test_case/
d=d01
hourSIMULATION=01
clean= echo ${day} | sed -e 's/-//g'
sed -e 's/<domine>/'$d'/g' -e 's/<data-initial>/'$clean$value'/g' -e 's/<hour-SIMULATION>/'$hourSIMULATION'/g' run_prhours > run_pr
done
done
The string $dayclean is empty when I check inside run_pr, do you know what could be the reason?
You are using days[i++] but no i is defined anywhere. Not sure what you want to do with ${days[i++]} but $days is just a string containing "$data $data1".
You probably want to say days=($data $data1) to create an array.
Also, you can say for hour in {00.23} instead of being explicit on the numbers.
Then, you want to loop through the hours and then through the days. For this, use a nested loop:
days=("$data" "$data1") #create an array properly
for value in {00..23}; do
for day in "${days[#]}"; do
cp "/path/mps_${day}_${value}:00:00" /another/path/test_case/
done
done

Using Bash to Generate Lotto Numbers

I've got a simple bash script that generates the following:
These are your winning lottery numbers:
27 6 29 17 15 47
19 16 33 15 20 14
29 34 48 19 33 40
Here is the code for it:
#!/bin/bash
tickets="$1"
function get_tickets { printf "How many tickets are you going to get? "; read tickets;}
function gen_numbers { printf "\nThese are your winning lottery numbers: \n";
for ((z=1 ; z<=tickets ; z++)); do
for ((i=0; i<6; i++ )); do
x=`echo $[ 1 + $[ RANDOM % 49 ]]`;
printf "\t $x";
done;
printf "\n"
done;
printf "\n"; }
############################
if [[ -z $tickets ]] ; then
get_tickets
gen_numbers
else
gen_numbers
fi
My question is, does anyone know how to modify it to prevent duplicate numbers on each row from appearing? I am guess I'd use uniq, and an array, but am not sure how that would look. Any advice would be appreciated; thanks!
This is just a script for fun.
Your attempt is pretty good. However, I think it can be easier and safer to get random values by using the shuf command:
$ shuf -i 1-49 -n18 | xargs -n6
39 42 43 7 14 23
10 27 5 13 49 8
31 36 19 47 28 4
shuf -i X-Y -nZ gives Z random numbers in between X and Y. Then xargs -nT formats them in groups of T numbers per line.
Update
Now I see the comment:
Yes; to avoid duplicate numbers within a row (by ticket).
In that case, you can simply do shuf -i 1-49 -n6 to get 6 random numbers. The output is line separated, so you can use tr '\n' ' ' to make it space separated.
In case you want many rows, for example 5, you can do:
for i in {1..5}; do shuf -i 1-49 -n6; done | xargs -n6
Sample output:
$ for i in {1..5}; do shuf -i 1-49 -n6; done | xargs -n6
4 45 12 42 37 46
42 20 29 22 12 5
40 41 14 28 4 2
35 24 16 22 2 39
14 46 47 20 21 41
To avoid duplicates by row use an array to mark the used values. Here is a modified version of your script:
for ((z = 1; z<=tickets ; z++)); do
i=0
used=( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 )
for (( ; i<6; )); do
x=`echo $[ 1 + $[ RANDOM % 49 ]]`;
if [ ${used[$x]} -eq 0 ]
then
printf "\t $x";
i=$((i + 1))
used[$x]=1
fi
done;
printf "\n"
done;
printf "\n"; }
EDIT: Explanation.
The idea is very simple, we use the array to mark those values already used.
Initially all the elements (50 elements that corresponds to the values [0, 49]) of the array are set to zero, i.e. all the numbers are available.
For each random number x we check if that number is available (used[x] = 0), if so we increase the counter i and mark that value (used[x] = 1) to avoid repetitions.
If the value is already taken simply try again and again until an unused value is found.
Check this one, it's quite good example of implementing lotto numbers generator using just JavaScript: https://lotto6aus49online.de/#zaufallszahlen; you can stop the generator by your own click for every number, which adds additional randomness into the algorithm. hope you like it!

shell programming: define array including zero-padded values

I just started using shell programming. I want to automatically change directories and then rename some files in there. Here's my problem: The name of the directories are numbered but directories < 10 are zero-padded (01 02...09). How can I define an array using some sort of sequencing without typing each directory name manually?
This is what I've tried so far:
array = (printf "%.2d " {1..8} {11..27} {29..32} {34..50}) ## should say 01 02 03 ..08 11..27 29..32 34..50
for i in "${array[#]}"
do
echo "dir_a/dir_b/sub$i/dir_c/"
done
However, it doesn't work and the result looks like: "subprintf", "sub%.2s", "sub1" etc.
Can you help me there?
In a next step I want to filter certain numbers in the array, e.g. 03, 09, 10, 28, 33 as these directories don't exist. Is there some easy solution to create such an array without concatenating 5 separate arrays?
Many thanks in advance,
Kati
Is there a need to use arrays? Otherwise, for bash 4, you can do
for i in {01..08} {11..27} {29..32} {34..50}; do
echo "dir_a/dir_b/sub${i}/dir_c/"
done
For an older version of bash you have to add the 0 yourself:
for i in 0{1..8} {11..27} {29..32} {34..50}; do
echo "dir_a/dir_b/sub${i}/dir_c/"
done
Of course, if you want to have an array, you can do
array=({01..08} {11..27} {29..32} {34..50})
or
array=(0{1..8} {11..27} {29..32} {34..50})
You could do this:
declare -a dirs=('01' '02' '03' '04' '05' '06' '07' '08')
echo ${dirs[#]}
01 02 03 04 05 06 07 08
# Make up next sequence
declare -a b=`seq 11 18`
echo ${b[#]}
11 12 13 14 15 16 17 18
# Add sequences together
dirs=("${dirs[#]}" ${b})
echo ${dirs[#]}
01 02 03 04 05 06 07 08 11 12 13 14 15 16 17 18
find [0-9][0-9] -type d | while read dirname
do
if [ $(echo "${dirname}" | sed -n '/01/p') ]
then
cd "${dirname}"
mv foo bar
cd ..
fi
done
Then you can just write another elif and sed check for every directory which contains files you want to rename. I know it's not what you asked for, but it is infinitely simpler. If you're allowed to, I'd also strongly recommend renaming that directory tree, as well.
#/bin/bash
raw=({01..08} {11..27} {29..32} {34..50})
filter=(03 09 10 28 33)
is_in() {
for e in "${#:2}"; do [[ "$e" == "$1" ]] && return 0; done
return 1
}
for i in ${raw[#]}; do
is_in $i ${filter[#]} || echo "dir_a/dir_b/sub$i/dir_c"
done
It'll take the numbers in the raw array and exclude every occurance of the ones in the filter array.

Bash random number generator where number is not in array

I am attempting to create a random number generator that generates a number between 1 and 99 but not any number that has already been generated.
In the script array1 contains the numbers already generated. To make it easier to test I have reduced the random number range to 0 - 14 and manually created an array.
I am quite new to bash scripting and am picking it up with a couple of books and the internet.
I have tried a mixture of ideas, the one that seems to make most sense is
array1=( 1 2 3 6 7 8 9 10 11 12 13 )
func1() {
for var in "${array1[#]}"
do
echo $var
done
}
rnd=$[ $RANDOM % 14 ]
until [ $rnd != func1 ]
do
rnd=$[ $RANDOM % 14 ]
done
echo $rnd
however I know the problem is on line 9 the shell sees the following code:
until [ $rnd != 1 2 3 6 7 8 9 10 11 12 13 ]
I know that the solution is that line 9 needs to be:
until [ $rnd != 1 ] && [ $rnd != 2 ] && [ $rnd != 3 ] && ...
I just don't know how to make this happen automatically from the array. The array does vary in length depending on how many numbers have been generated.
Any help will be greatly appreciated!
This is something that I found difficulty doing in bash. The approach I came up with is to have func1() return true or false and modify the array to remove the number that has been picked.
array=( {1..15} )
func1() {
local pick="$1"
found=1
total=${#array[#]}
for ((i=0;i<total;i++)); do
if (( pick == ${array[i]} )); then
echo $pick
array=( ${array[#]:0:i} ${array[#]:((i + 1)):$total})
found=0
break
fi
done
return $found
}
numbers=3
for ((x=0;x<numbers;x++)); do
until func1 $(( $RANDOM % ( ${#array[#]} ) )); do
continue
done
done
As noted in one of the comments, using the Knuth Shuffle is an excellent way to do this
#!/bin/bash
shuffle() {
local i tmp size max rand
# Code from http://mywiki.wooledge.org/BashFAQ/026
# $RANDOM % (i+1) is biased because of the limited range of $RANDOM
# Compensate by using a range which is a multiple of the array size.
size=${#array[*]}
max=$(( 32768 / size * size ))
for ((i=size-1; i>0; i--)); do
while (( (rand=$RANDOM) >= max )); do :; done
rand=$(( rand % (i+1) ))
tmp=${array[i]} array[i]=${array[rand]} array[rand]=$tmp
done
}
# Fill an array with values 1 to 99
array=({1..99});
# Shuffle the array at random
shuffle
# Echo shuffled array
echo ${array[#]}
Output
$ ./knuth
58 78 6 37 84 79 81 43 50 25 49 56 99 41 26 15 86 11 96 90 76 46 92 70 87 27 33 91 1 2 73 97 65 69 42 32 39 67 72 52 36 64 24 88 60 35 83 89 66 30 4 53 57 28 75 48 40 74 18 23 45 61 20 31 21 16 68 80 62 8 98 14 7 19 47 55 22 85 59 17 77 10 63 93 51 54 95 82 94 9 44 38 13 71 34 29 5 3 12
You can also use the -R switch to sort, if your version of sort supports it:
for x in {1..99} ; do echo $x ; done | sort -R

Resources