For loop with multiple conditions in bash - bash

first of all, i've read the question for loop with multiple conditions in Bash scripting but it does not work for what i intend to do. In the following script, a first for loop assign f quantity of arrays to a pair of variables (CON_PERC and CON_NAME)
f=0
for i in "${container[#]}"; do
CON_PERC[$f]=$(awk '{print int($2+0)}' <<< ="${container[$f]}") #CON_PERC[0] = 2; CON_PERC[1] = 0
CON_NAME[$f]=$(awk '{print $1}' <<< "${container[$f]}") #CON_NAME[0] = hi; CON_NAME[1] = bye
((f++))
done
what i need to do now, is in a separate loop, check every array of both variables and print themm. what would be the best way to do it?
what i tough is something like this
e=0
for ((x in "$CON_PERC[#]" && z in "$CON_NAME[#]")); do
echo "${CON_NAME[$e]} ${CON_PERC[$e]}"
((e++))
done
but it seems that for ((i in "$CON_PERC[#]" && e in "$CON_NAME[#]")) isnt valid in bash.
The question is, what is the best way to approach this, should i exclusively use a nested loop or is other way around it?

Here you have one way :
#!/bin/bash
CON_PERC=(1 2 3)
CON_NAME=("Hello" "Hallo" "Hola")
for item in "${CON_PERC[#]}" "${CON_NAME[#]}"
do
printf "Item : $item\n"
done
This will print :
Item : 1
Item : 2
Item : 3
Item : Hello
Item : Hallo
Item : Hola
Hope it helps!
Edit :
If you want you want you can use a traditional for loop as well. Im assuming both arrays will have the same size :
#!/bin/bash
CON_PERC=(1 2 3)
CON_NAME=("Hello" "Hallo" "Hola")
for (( i=0 ; i < "${#CON_PERC[#]}"; i++ ))
do
echo "${CON_PERC[i]} : ${CON_NAME[i]}"
done

You need to nest them like this (untested in your examples)
for x in "$CON_PERC[#]";
do
for z in "$CON_NAME[#]";
do
echo ${CON_NAME[$e]} ${CON_PERC[$e]}
((e++))
done
done
e.g.:
for x in {a..b};
do
for y in {c..d};
do
echo $x $y
done
done
result:
a c
a d
b c
b d

You could loop through the array keys of one of your arrays and use this key to get the value:
CON_PERC=( 01 02 03 04 )
CON_NAME=( one two three four )
for i in "${!CON_NAME[#]}"; do
printf '%s %s\n' "${CON_NAME[i]}" "${CON_PERC[i]}"
done
Output:
one 01
two 02
three 03
four 04

Related

bash: get size and [x,y]-element from a list of different size arrays

BASH: I have a list of array of different size from an external config sourced file:
declare -a line0=( 00 01 02 )
declare -a line1=( 10 11 )
...
declare -a line9=( 90 91 92 93 )
rows=9
Getting the size of a single array works as so:
${#line0[#]}
this can be practical only for few arrays like in the example.
I need to get the array size in a for loop. I tryed this:
for ((r=0;r<rows;r++)) do
line="line$r"
echo line:$line
cols="${#$line[#]}" # 1st assignment
cols="${#line$r[#]}" # 2nd assignment
done
but got 'bad substitution' error for both assignment.
Then, supposing to know the max cols value, I need to extract the single element from arrays with two nested loops. I tryed so:
cols=4
for ((r=0;r<rows;r++)) do
line="line$r"
echo line:$line
for ((c=0;c<cols;c++)) do
val=${$line[$c]} # 1st assignment
val=${line$r[$c]} # 2nd assignment
echo val:$val
done
done
but got 'bad substitution' error for both assignment.
Edit: what is the right method to get size and [x,y]-element from a list of different size arrays?
I looked for other questions, but there are solutions for same sized arrays only
You can make use of indirect reference with declare -n ref=varname.
Would you please try:
for ((r = 0; r < rows; r++)); do
declare -n line="line$r" # now "line" will be a reference to the array
cols="${#line[#]}" # you can access the array with the name "line"
echo "line:$r cols:$cols"
done
BTW it might be better to say rows=10, because the count of rows is 10 (IMHO).
Another method could be
#!/bin/bash
source configfile
for arrname in ${!line*}; do
[[ $arrname =~ ^line[0-9]+$ ]] || continue
declare -n arrptr=$arrname
printf 'line:%d size:%d\n' "${arrname#line}" "${#arrptr[#]}"
done
This assumes all arrays whose names match the regular expression ^line[0-9]+$ are of interest.
declare -p $(seq 1 "$rows"|xargs -I {} echo line{})|awk -F'[][ =]' '{print $3,$(NF-2)+1}'
line1 2
line2 2
line3 3
line4 3
line5 2
line6 2
line7 1
line8 2
line9 4
seq 1 "$rows"|xargs -I {} echo line{} list all array-names
declare -p <array-name> display the attributes and values
awk -F'[][ =]' '{print $3,$(NF-2)+1}' print array-name and size
like so is perfect:
declare -a line0=( 00 01 02 )
declare -a line1=( 10 11 )
...
declare -a line9=( 90 91 92 93 )
rows=10
for ((r=0;r<rows;r++)) do
declare -n line="line$r"
#echo line:"$line"
cols="${#line[#]}"
echo cols:$cols
for ((c=0;c<cols;c++)) do
val=${line[$c]}
echo r:$r c:$c val:$val
done
done
The only strange think is that printing "line" return something non-sense:
echo line:"$line"
show (seems 1st element of lineN):
line:00
line:10
...
but I do not need to print that, only to use as refs, works, thanks

How to sort 2 arrays in bash

I want to sort 2 arrays at the same time. The arrays are the following: wordArray and numArray. Both are global.
These 2 arrays contain all the words (without duplicates) and the number of the appearances of each word from a text file.
Right now I am using Bubble Sort to sort both of them at the same time:
# Bubble Sort function
function bubble_sort {
local max=${#numArray[#]}
size=${#numArray[#]}
while ((max > 0))
do
local i=0
while ((i < max))
do
if [ "$i" != "$(($size-1))" ]
then
if [ ${numArray[$i]} \< ${numArray[$((i + 1))]} ]
then
local temp=${numArray[$i]}
numArray[$i]=${numArray[$((i + 1))]}
numArray[$((i + 1))]=$temp
local temp2=${wordArray[$i]}
wordArray[$i]=${wordArray[$((i + 1))]}
wordArray[$((i + 1))]=$temp2
fi
fi
((i += 1))
done
((max -= 1))
done
}
#Calling Bubble Sort function
bubble_sort "${numArray[#]}" "${wordArray[#]}"
But for some reason it won't sort them properly when large arrays are in place.
Does anyone knows what's wrong with it or an other approach to sort the words with the corresponding number of appearance with or without arrays?
This:
wordArray = (because, maybe, why, the)
numArray = (5, 12, 20, 13)
Must turn to this:
wordArray = (why, the, maybe, because)
numArray = (20, 13, 12, 5)
Someone recommended to write the two arrays side by side in a text file and sort the file.
How will it work for this input:
1 Arthur
21 Zebra
to turn to this output:
21 Zebra
1 Arthur
Assuming the arrays do not contain tab character or newline character, how about:
#!/bin/bash
wordArray=(why the maybe because)
numArray=(20 13 12 5)
tmp1=$(mktemp tmp.XXXXXX) # file to be sorted
tmp2=$(mktemp tmp.XXXXXX) # sorted result
for (( i = 0; i < ${#wordArray[#]}; i++ )); do
echo "${numArray[i]}"$'\t'"${wordArray[i]}" # write the number and word delimited by a tab character
done > "$tmp1"
sort -nrk1,1 "$tmp1" > "$tmp2" # sort the file by number in descending order
while IFS=$'\t' read -r num word; do # read the lines splitting by the tab character
numArray_sorted+=("$num") # add the number to the array
wordArray_sorted+=("$word") # add the word to the array
done < "$tmp2"
rm -- "$tmp1" # unlink the temp file
rm -- "$tmp2" # same as above
echo "${wordArray_sorted[#]}" # same as above
echo "${numArray_sorted[#]}" # see the result
Output:
why the maybe because
20 13 12 5
If you prefer not to create temp files, here is the process substitution version, which will run faster without writing/reading temp files.
#!/bin/bash
wordArray=(why the maybe because)
numArray=(20 13 12 5)
while IFS=$'\t' read -r num word; do
numArray_sorted+=("$num")
wordArray_sorted+=("$word")
done < <(
sort -nrk1,1 < <(
for (( i = 0; i < ${#wordArray[#]}; i++ )); do
echo "${numArray[i]}"$'\t'"${wordArray[i]}"
done
)
)
echo "${wordArray_sorted[#]}"
echo "${numArray_sorted[#]}"
Or simpler (using the suggestion by KamilCuk):
#!/bin/bash
wordArray=(why the maybe because)
numArray=(20 13 12 5)
while IFS=$'\t' read -r num word; do
numArray_sorted+=("$num")
wordArray_sorted+=("$word")
done < <(
paste <(printf "%s\n" "${numArray[#]}") <(printf "%s\n" "${wordArray[#]}") | sort -nrk1,1
)
echo "${wordArray_sorted[#]}"
echo "${numArray_sorted[#]}"
You need numeric sort for the numbers. You can sort an array like this:
mapfile -t wordArray <(printf '%s\n' "${wordArray[#]}" | sort -n)
But what you actually need is something like:
for num in "${numArray[#]}"; do
echo "$num: ${wordArray[j++]}"
done |
sort -n k1,1
But, earlier in the process, you should have used only one array, where the word and frequency (or vice versa) are key value pairs. Then they always have a direct relationship, and can be printed similarly to the for loop above.

Taking a count from file, I want to print no of variables using shell/bash

Taking count from file, say if count = 5, I want to print 5 variables. i.e. A B C D E.
If count = 2, Print 2 variables A B, etc.
I have tried using the ASCII values but couldn't go through it.
for i in {1..5}; do
count=5; a=0;
printf "\x$(printf %x '65+$a')";
count=count+1;
done
if count = 5, I want to print 5 variables. i.e. A B C D E. If count = 2, Print 2 variables A B, etc.
Here's a program that matches your style that does what you are looking for:
a=0
for i in {1..5}; do
printf "\x$(printf %x $(( 65 + a )) )";
a=$((a+1));
done
The first thing to note is that in order to do math in bash, you'll need to use the $(( )) operation. Above, you can see I replaced you '65+$a' with $(( 65 + a )) . That's the big news that you need to get math done.
There were a couple of other little issues, but you were stuck on the $(()) stuff so they weren't clear yet. Incidentally, the 'a' variable can be completely removed from the program to just use the 'i' variable like this:
for i in {1..5}; do
printf "\x$(printf %x $(( 64 + i )) )";
done
I had to change the constant to 64 since we are now counting starting at 1.
The {1..5} expression is a good short cut for 1 2 3 4 5, but you won't be able to put a variable into it. So, if you need to add a count variable back in, consider using the seq program instead like this:
count=$1
for i in $(seq 1 $count); do
printf "\x$(printf %x $(( 64 + i )) )";
done
Note that $() is different than the math operator $(()). $() runs a subcommand returning the results.
method 1: simple brace expansion
#!/bin/bash
# generate a lookup table
vars=( - $(echo {A..Z}) )
# use the elements
for i in {1..5}; do
echo ${vars[$i]}
done
{A..Z} generates 26 strings: A, B, ..., Z
which get stored in an array variable by vars=(...)
we prepend a - that we'll ignore
we can then do 1-based indexing into the array
limited to 26 variables (or whatever range we choose)
method 2: multiple brace expansion to generate arbitrary long variables
#!/bin/bash
if [[ ! $1 =~ ^[0-9]+$ ]]; then
echo "Usage: $0 count"
exit
fi
cmd='{A..Z}'
for (( i=$1; i>26; i=i/26 )); do
cmd="${A..Z}$cmd"
done
vars=( $(eval echo $cmd) )
for (( i=0; i<$1; i++ )); do
echo ${vars[$i]}
done
i/26 does integer division (throws away the remainder)
I'm lazy and generate "more than enough" variables rather than attempting to calculate how many is "exactly enough"
{a..b}{a..b}{a..b} becomes aaa aab aba abb baa bab bba bbb
using eval lets us do the brace expansion without knowing in advance how many sets are needed
Sample output:
$ mkvar.sh 10000 |fmt -64 | tail -5
ORY ORZ OSA OSB OSC OSD OSE OSF OSG OSH OSI OSJ OSK OSL OSM
OSN OSO OSP OSQ OSR OSS OST OSU OSV OSW OSX OSY OSZ OTA OTB
OTC OTD OTE OTF OTG OTH OTI OTJ OTK OTL OTM OTN OTO OTP OTQ
OTR OTS OTT OTU OTV OTW OTX OTY OTZ OUA OUB OUC OUD OUE OUF
OUG OUH OUI OUJ OUK OUL OUM OUN OUO OUP

I stored values in Multi Dimensional Array. When printing the values, it's printing the second iteration values and first iteration is being replaced

This block of code is looping through a file and loading each word into a multi dimensional array.
lcv=0
declare -A db
while read line;
do
lcv1=0
echo $line
for i in $line;
do
db[$lcv,$lcv1]=$i
echo $lcv,$lcv1,${db[$lcv,$lcv1]};
#echo ${db[$lcv]}
((++lcv1))
done
((++lcv))
done < data.txt # File Contains records of 4 fields.
echo ${db[0,1]}
echo ${db[0,0]}
Little pseudo 2D array using bash
I just re-use your algorithm, whiping all echo and useless steps.
#!/bin/bash
unset x y db
y=0
declare -A db
while read line ;do
for i in $line ;do
db[$((x++)),$y]=$i
done
((y++))
x=0
done <<<$'0 1 2 3\n4 5 6 7\n8 9 a b\nc d e f'
Now if you
declare -p db x y
bash will print:
declare -A db='([0,0]="0" [0,1]="4" [0,2]="8" [0,3]="c" [3,3]="f" [3,2]="b" [3,1]="7" [3,0]="3" [2,2]="a" [2,3]="e" [2,0]="2" [2,1]="6" [1,1]="5" [1,0]="1" [1,3]="d" [1,2]="9" )'
declare -- x="0"
declare -- y="4"
At this point, I just wanna purpose to change 9th line: ((y++)) by ((y++,maxx=maxx>x?maxx:x)). This will populate maxx (to 4 in this sample)
Then inverting the array:
for i in {0..4};do # this syntax is nice, but don't support variables
for((j=0;j<y;j++)){ # this syntaxe could use variables
echo -n ${db[$i,$j]}\
}
echo
done
will print:
0 4 8 c
1 5 9 d
2 6 a e
3 7 b f
If the data.txt contains this:
$ cat data.txt
l0val0 l0val1 l0val2 l0val3
l1val0 l1val1 l1val2 l1val3
l2val0 l2val1 l2val2 l2val3
l3val0 l3val1 l3val2 l3val3
l4val0 l4val1 l4val2 l4val3
l5val0 l5val1 l5val2 l5val3
Your program produce this:
$ ./script
l0val0 l0val1 l0val2 l0val3
0,0,l0val0
0,1,l0val1
0,2,l0val2
0,3,l0val3
l1val0 l1val1 l1val2 l1val3
1,0,l1val0
1,1,l1val1
1,2,l1val2
1,3,l1val3
l2val0 l2val1 l2val2 l2val3
2,0,l2val0
2,1,l2val1
2,2,l2val2
2,3,l2val3
l3val0 l3val1 l3val2 l3val3
3,0,l3val0
3,1,l3val1
3,2,l3val2
3,3,l3val3
l4val0 l4val1 l4val2 l4val3
4,0,l4val0
4,1,l4val1
4,2,l4val2
4,3,l4val3
l5val0 l5val1 l5val2 l5val3
5,0,l5val0
5,1,l5val1
5,2,l5val2
5,3,l5val3
l0val1
l0val0
That goes to show that the value of $lcv selects each row (line), and the value of $lcv1 selects each word (record) divided on spaces or tabs.
It is working correctly from what I can see.
If we add this lines at the end of the script:
echo "end of first script"
for i in {0..5}; do
for j in {0..3}; do
printf 'db[%s,%s]=%s ' "$i" "$j" "${db[$i,$j]}"
done
echo
done
echo
declare -p db
We will get this output:
end of first script
db[0,0]=l0val0 db[0,1]=l0val1 db[0,2]=l0val2 db[0,3]=l0val3
db[1,0]=l1val0 db[1,1]=l1val1 db[1,2]=l1val2 db[1,3]=l1val3
db[2,0]=l2val0 db[2,1]=l2val1 db[2,2]=l2val2 db[2,3]=l2val3
db[3,0]=l3val0 db[3,1]=l3val1 db[3,2]=l3val2 db[3,3]=l3val3
db[4,0]=l4val0 db[4,1]=l4val1 db[4,2]=l4val2 db[4,3]=l4val3
db[5,0]=l5val0 db[5,1]=l5val1 db[5,2]=l5val2 db[5,3]=l5val3
declare -A db=([1,1]="l1val1" [1,0]="l1val0" [1,3]="l1val3" [1,2]="l1val2" [0,0]="l0val0" [0,1]="l0val1" [0,2]="l0val2" [0,3]="l0val3" [5,1]="l5val1" [5,0]="l5val0" [5,3]="l5val3" [5,2]="l5val2" [3,3]="l3val3" [3,2]="l3val2" [3,1]="l3val1" [3,0]="l3val0" [2,2]="l2val2" [2,3]="l2val3" [2,0]="l2val0" [2,1]="l2val1" [4,0]="l4val0" [4,1]="l4val1" [4,2]="l4val2" [4,3]="l4val3" )
Now, the question is: What do you think that is wrong?.

How can I shift digits in bash?

I have a homework assignment that is asking to shift a decimal number by a specified amount of digits. More clearly this bash script will take two input arguments, the first is the number(maximum 9 digits) that the shift will be performed on and the second is the number(-9 to 9) of digits to shift. Another requirement is that when a digit is shifted off the end, it should be attached to the other end of the number. One headache of a requirement is that we cannot use control statements of any kind: no loops, no if, and switch cases.
Example: 12345 3 should come out to 345000012 and 12345 -3 should be 12345000
I know that if I mod 12345 by 10^3 I get 345 and then if I divide 12345 by 10^3 I get 12 and then I can just concatenate those two variables together to get 34512. I am not quite sure if that is exactly correct but that is the closest I can get as of now. As far as the -3 shift, I know that 10^-3 is .001 and would work however when I try using 10^-3 in bash I get an error.
I am just lost at this point, any tips would be greatly appreciated.
EDIT: After several hours of bashing (pun intended) my head against this problem, I finally came up with a script that for the most part works. I would post the code right now but I fear another student hopelessly lost might stumble upon it. I will check back and post what I came up with in a week or two. I was able to do it with mods and division. Thank you all for the responses, it really helped me to open up and think about the problem from different angles.
Here's a hint:
echo ${string:0:3}
echo ${#string}
Edit (2011-02-11):
Here's my solution. I added some additional parameters with defaults.
rotate-string ()
{
local s=${1:-1} p=${2:--1} w=${3:-8} c=${4:-0} r l
printf -vr '%0*d' $w 0 # save $w zeros in $r
r=${r//0/$c}$s # change the zeros to the character in $c, append the string
r=${r: -w} # save the last $w characters of $r
l=${r: -p%w} # get the last part of $r ($p mod %w characters)
echo "$l${r::w-${#l}}" # output the Last part on the Left and the Right part which starts at the beginning and goes for ($w minus the_length_of_the_Left_part) characters
}
usage: rotate-string string positions-to-rotate width fill-character
example: rotate-string abc -4 9 =
result: ==abc====
Arguments can be omitted starting from the end and these defaults will be used:
fill-character: "0"
width: 8
positions-to-rotate: -1
string: "1"
More examples:
$ rotate-string
00000010
$ rotate-string 123 4
01230000
Fun stuff:
$ for i in {126..6}; do printf '%s\r' "$(rotate-string Dennis $i 20 .)"; sleep .05; done; printf '\n'
$ while true; do for i in {10..1} {1..10}; do printf '%s\r' "$(rotate-string : $i 10 .)"; sleep .1; done; done
$ while true; do for i in {40..2} {2..40}; do printf '%s\r' "$(rotate-string '/\' $i 40 '_')"; sleep .02; done; done
$ d=0; while true; do for i in {1..10} {10..1}; do printf '%s\r' "$(rotate-string $d $i 10 '_')"; sleep .02; done; ((d=++d%10)); done
$ d=0; while true; do for i in {1..10}; do printf '%s\r' "$(rotate-string $d $i 10 '_')"; sleep .2; ((d=++d%10)); done; done
$ shape='▁▂▃▄▅▆▇█▇▆▅▄▃▂▁'; while true; do for ((i=1; i<=COLUMNS; i++)); do printf '%s\r' "$(rotate-string "$shape" $i $COLUMNS ' ')"; done; done
In the absence of control structures, you need to use recursion, with index values as "choice selections", which is how functional programming often works.
#!/bin/sh
#
# cshift NUMBER N
cshift() {
let num=10#$1
num=`printf '%09d' $num`
lshift="${num:1:8}${num:0:1}"
rshift="${num:8:1}${num:0:8}"
next=( "cshift $lshift $(($2 + 1))" "echo $num" "cshift $rshift $(( $2 - 1 ))" )
x=$(( $2 == 0 ? 1 : $2 < 0 ? 0 : 2 ))
eval "${next[x]}"
}
cshift $1 $2
and, the testing:
$ for ((i=-9;i<=9;i++)); do cshift 12345 $i ; done
000012345
500001234
450000123
345000012
234500001
123450000
012345000
001234500
000123450
000012345
500001234
450000123
345000012
234500001
123450000
012345000
001234500
000123450
000012345
You can also do some math on the indexes and avoid the recursion, but I don't mind making the computer work harder so I don't have to. It's easy to think of how to do the shift by one in either direction, and then I use an evaluated choice that is selected by the signum of the shift value, outputting a value and stopping when the shift value is zero.

Resources