Unix shell iterate over two lists with unequal number of values - arrays

How would you produce output that loops the second list over the first? Both lists are files with a values delimited by lines.
list1:
1
2
5
6
8
10
13
list2:
a
b
c
d
Output:
1 a
2 b
5 c
6 d
8 a
10 b
13 c

Just read the shortest file and store each line in an array.
Then read the second line by line and print the i-th line mod the number of lines.
This is a quick bash solution:
while read -r; do
shortfile+=("$REPLY"); ((i++))
done < /path/to/shortfile
lines=$i i=0
while read -r; do
printf '%s %s\n' "$REPLY" "${shortfile[i++%lines]}"
done < /path/to/longfile

Related

Taking x number integers and putting them into an array

I need to take x amount of input lines (x is specified by the user) then put them into a 2d array. Each line contains x amount of integers separated by spaces.
For example;
Input:
3
4 3 1
6 5 2
9 7 3
I need to take that input and put them into a 2d array, how do I do this?
Assuming your numbers are separated exactly by one space:
n = int(input('enter size'))
print([[int(i) for i in input().split(' ')]
for __ in range(n)])
Suppose you have your input stored in a file named 'input.txt'
n=2 #number specified by user
with open('input.txt', 'r') as file:
result = [[int(char) for char in lines.split(' ')]for lines in file.read().splitlines()[:2*n:2] ]

delete elements in array bash

im trying to delete two elements at same time in my array in bash script
my code is
elegidos = (1 2 3 4 5 6 7)
i=0
j=${#elegidos[#]}
delete=($i $j)
while [ $i -le $j ]; do
#elegidosmenosdos=${elegidos[#]/$i:$j}
echo ${elegidos[#]/$delete}
delete=($i $j)
let "i++"
let "j--"
done
the output that i have is
1 2 3 4 5 6 7
1 2 3 4 5 6 7
2 3 4 5 6 7
1 3 4 5 6 7
and i need 21 different combinations with five elements using seven numbers
output example
1 2 3 4 5
2 3 4 5 6
7 6 5 4 3
.
.
.
.
.(21 MORE)
Well, it took a minute to suss out that you were simply wanting to do an end-to-middle delete of two-elements at a time working from the ends of the array, deleting the end nodes at the same time, increment/decrement your counters and repeat until you reached the middle. Why you want to do it this way is a bit of a mystery. You can, of course, simply unset elegidos and unset all values at once. However, if you want to work in from both ends -- that fine too if you have a purpose for it.
You have several problems in your script. In bash, all arrays are zero indexed. Your array has 7-elements, so the valid indexes are 0-6. Therefore, you j was wrong to begin with. You needed to subtract 1 from the number of elements to get the index for the end-element, e.g.
i=0
j=$((${#elegidos[#]} - 1))
bash provides a C-style loop that can greatly simplify your task. While you are free to use a while a C-style loop can handle the index increment and decrement seamlessly, e.g.
for ((; i <= j; i++, j--)); do
note the i <= j. If you have an odd-number of elements in the array, on your last iteration, you will simply be deleting one-value instead of two. To handle that condition you need a simple test within the loop to check whether [ "$i" = "$j" ] (or using the arithmetic comparison (( i == j ))).
Putting that altogether, you could refine your element removal to empty the array two-elements at a time to something similar to the following:
#!/bin/bash
elegidos=(1 2 3 4 5 6 7)
i=0
j=$((${#elegidos[#]} - 1))
delete=($i $j)
for ((; i <= j; i++, j--)); do
declare -p elegidos
unset elegidos[$i]
[ "$i" != "$j" ] && unset elegidos[$j]
delete=($i $j)
done
Example Use/Output
$ bash array_del.sh
declare -a elegidos='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7")'
declare -a elegidos='([1]="2" [2]="3" [3]="4" [4]="5" [5]="6")'
declare -a elegidos='([2]="3" [3]="4" [4]="5")'
declare -a elegidos='([3]="4")'
You can see above, that on the first 3-iterations, both end-elements are removed. However, notice on the last removal (there originally being and odd number of elements, only the single-last value is removed on the final iteration.
Look things over and let me know if I captured what you were attempting and whether you have any further questions. If your intent was something else, drop a comment and I'm happy to help further.

Get values of columns of a file with tabs and newlines separating them read via bash script to an array

Hello I have been trying to get the numbers in the columns of a file for two days by reading a file via a bash script. Here is the file sample.txt
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
By column I mean i.e the first column is
1
9
6
3
3
6
I need to have the column elements each be in a given array col1 or col2 etc so that I can manipulate the values further.
Here's what I have done so far using while loop I have read the contents of the file assigning them each line to an array.
If I set IFS=$'\n'
while read -a line
do
IFS=$'\n'
#I can get the whole column 1 with this
echo ${line[0]}
#for column 2 I can get it by this an the others too
echo ${line[1]}
done < sample.txt
Now that may seem good as i thought but since I want to calculate averages of the columns putting in another loop like a for loop becomes impossible since ${line[0]} has all the elements in column 1 but they are all as a single string (i have tried to observe) that cannot be acted upon.
What would be the best way to get those elements be members of a given array and then compute the averages on them. help appreciated .
In bash I'd write
declare -A cols
n=0
while read -ra fields; do
for ((i=0; i<${#fields[#]}; i++)); do
cols[$i,$n]=${fields[i]}
((n[i]++))
done
done < sample.txt
read -a reads the fields of the line into the named array.
I'm using cols as an associative array to fake a multi-dimensional array. That's way easier to deal with than using a dynamic variable name:
eval "column${i}[$n]=\${fields[$i]}"

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

awk, declare array embracing FNR and field, output

I would like to declare an array of a certain number of lines, that means from line 10 to line 78, as an example. Could be other number, this is just an example.
My sample gives me that range of lines on stdout but sets "1" in between that lines. Can anybody tell me how to get rid of that "1"?
Sample as follows should go to stdout and embraces the named lines.
awk '
myarr["range-one"]=NR~/^2$/ , NR~/^8$/;
{print myarr["range-one"]};' /home/$USER/uplog.txt;
That is giving me this output:
0
12:33:49 up 3:57, 2 users, load average: 0,61, 0,37, 0,22 21.06.2014
1
12:42:02 up 4:06, 2 users, load average: 0,14, 0,18, 0,19 21.06.2014
1
12:42:29 up 4:06, 2 users, load average: 0,09, 0,17, 0,19 21.06.2014
1
12:43:09 up 4:07, 2 users, load average: 0,09, 0,16, 0,19 21.06.2014
1
Second question: how to set in that array one field of FNR or line?
When I do it this way there comes up the field that I wanted
awk ' NR~/^1$/ , NR~/^7$/ {print $3, $11; next} ; ' /home/$USER/uplog.txt;
But I need an array, thats why I'm asking. Any hints? Thanks in advance.
What the example script does
awk '
myarr["range-one"]=NR~/^2$/ , NR~/^8$/;
{print myarr["range-one"]};'
Your script is one of the more convoluted and decidedly less-than-obvious pieces of awk that I've ever seen. Let's take a simple input file:
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
Line 11
Line 12
The output from that is:
0
Line 2
1
Line 3
1
Line 4
1
Line 5
1
Line 6
1
Line 7
1
Line 8
1
0
0
0
0
Dissecting your script, it appears that the first line:
myarr["range-one"]=NR~/^2$/ , NR~/^8$/;
is equivalent to:
myarr["range-one"] = (NR ~ /^#$/, NR ~ /^8$/) { print }
That is, the value assigned to myarr["range-one"] is 1 inside the range of line numbers where NR is equal to 2 and is equal to 8, and 0 outside that range; further, when the value is 1, the line is printed.
The second line:
{print myarr["range-one"]};
print the value in myarr["range-one"] for each line of input. Thus, on the first line, the value 0 is printed. For lines 2 to 8, the line is printed followed by the value 1; for lines after that, the value 0 is printed once more.
What the question asks for
The question is not clear. It appears that lines 10 to 78 should be printed. In awk, there are essentially no variable declarations (we can debate about function parameters, but functions don't seem to figure in this). Therefore, declaring an array is not an option.
awk -v lo=10 -v hi=78 'NR >= lo && NR <= hi { print }'
This would print the lines between line 10 and line 78. It would be feasible to save the values in an array (a in the examples below). Said array could be indexed by NR or with a separate index starting at 0 or 1:
awk -v lo=10 -v hi=78 'NR >= lo && NR <= hi { a[NR] = $0 }' # Indexed by line number
awk -v lo=10 -v hi=78 'NR >= lo && NR <= hi { a[i++] = $0 }' # Indexed from 0
awk -v lo=10 -v hi=78 'NR >= lo && NR <= hi { a[++i] = $0 }' # Indexed from 1
Presumably, you'd also have an END block to do something with the data.
The semicolons in the original are both unnecessary. The blank line is ignored, of course.

Resources