bash shell scripting transpose rows and columns - bash

I need help transposing a file that just simply has some numbers in them with rows and columns. I can't use awk shell or perl so it makes it kind of hard. I've been working on it for a couple of hours and can't get it working correctly. I tried a couple of other things but this is what I have right now. It runs, but it doesn't print out anything, so that leads me to conclude that something is wrong within my code. Also if you dont know by transpose if a file had :
1 2 3
4 5 6
... it would then print out
1 4
2 5
3 6
Here is my code:
if [ $# -gt 2 ]
then
echo"System error">&2
exit 1
elif [[ $# -eq 2 && -e "$2" && -r "$2" ]]
then
while read -a line; do
for ((i=0; i < "${#line[#]}"; i++)); do
a[$i]="${a[$i]} ${line[$i]}"
done
done < $2
for ((i=0; i < ${#a[#]}; i++)); do
echo ${a[i]}
done
fi

If possible use awk:
Source (file.txt):
1 2 3
4 5 6
Result:
1 4
2 5
3 6
Oneline awk sctript:
awk '{ for (i=1; i<=NF; i++) a[i]= (a[i]? a[i] FS $i: $i) } END{ for (i in a) print a[i] }' file.txt
It works same with
1 2 3 1 4 7
4 5 6 -> 2 5
7 3 6
and with
1 2 3 4 1 5
5 6 7 -> 2 6
3 7
4

Instead of writing a Bash function, we could use rs, written specifically for reshaping matrices. This command does exactly what you ask for:
rs -T

Related

Cat data_file n times [duplicate]

This question already has answers here:
How do I iterate over a range of numbers defined by variables in Bash?
(20 answers)
Closed 3 years ago.
I have a file like this:
1
2
3
I need to copy data n times with an empty line after each copy. I used these commands
#!/bin/sh
num=$(sed -n '1 p' FILE.txt)
for i in {1.. $num }; do cat distance_k.txt >> distance.txt; done
n is a number taken from another file 'FILE.txt' (FILE.txt have form like this :
90
Abcbaahjfh
...
However, it copied only 4 times.
Could you please help me?
Thank you so much!
bash shell
Output
1
2
3
1
2
3
1
2
3
1
2
3
1
2
3
...
Use another kind of loop:
num=$(sed -n '1 p' FILE.txt)
for (( i=0; i<num; i++ ))
do
cat distance_k.txt
printf '\n'
done >distance.txt
If you like, you can use for (( i=1; i<=num; i++ )) instead of for (( i=0; i<num; i++ )). It's the same if not using the value of i.

get column numbers that are equal to X

imagine that I've the following string:
1 0 1 1 1
a simple implementation to get the column numbers that are equal to "1" is:
for column_number in $(seq 1 5); do
zero_or_one=$(echo "1 0 1 1 1" | cut -d' ' -f$column_number)
if [ "$zero_or_one" -eq "1" ]; then
echo "$column_number"
fi
done
however, as my strings tend to be very long, that loop takes ages (~ 1min).
is there any other way using for example awk, sed, ..., to get the column numbers that are equal to "1" or "0" ?
expected output if I'm looking for "1":
1
3
4
5
expected output if I'm looking for "0":
2
It's not clear from your question but this MAY be what you want:
$ awk -v RS=' ' '$0{print NR}' <<<'1 0 1 1 1'
1
3
4
5
$ awk -v RS=' ' '!$0{print NR}' <<<'1 0 1 1 1'
2
This looks like the kind of thing that you should awk for:
awk '{ for (i = 1; i <= NF; ++i) if ($i == 1) print i }' <<<'1 0 1 1 1'
Loop through the fields, compare their value and print the ones that match.
That said, it's worth mentioning that you could improve the performance of your existing approach too:
while read -ra cols; do
for (( i = 1; i <= ${#cols[#]}; ++i )); do
[[ ${cols[i-1]} -eq 1 ]] && echo "$i"
done
done <<<'1 0 1 1 1'
This uses native shell commands rather than executing separate processes to obtain each value, so it will be much quicker than your loop.
Note that the array is zero-indexed, so I've used ${cols[i-1]} in order to obtain the same output.

while loop | reverse counting n....8 7 6 5 4 3 2 1

I am trying to get an automatic reverse counting with user input.
The error is I keep getting the first number in the sequence twice.
#!/bin/bash
#Print decremental numbers based on user input
#n...8 7 6 5 4 3 2 1
echo "Input number"
read k
while test $k != 0
do
echo "$k"
k="$(( k - 1 ))"
done
Output:
[root#localhost standalone_scripts]# . ./decr.sh
Input number
5
5
4
3
2
1
as is pointed out in the comments, it's displaying the user input. if you don't like the way it's displayed, then you can switch the order of the subtraction with the echo.
#!/bin/bash
#Print decremental numbers based on user input
#n...8 7 6 5 4 3 2 1
echo "Input number"
read k
while test $k != 0
do
k="$(( k - 1 ))"
echo "$k"
done
Here's an idiomatic Bash reformulation of the script:
Using read -p prints the prompt string and the user's input on the same line, which makes misinterpreting the user's input as being part of the script's output less likely (which is what prompted the OP's question).
This is probably preferable to using read -s, which suppresses echoing the user's input as it is being typed.
Using a Bash-native, C-style arithmetic for loop (for (( ...; ...; ... ))) avoids the concerns about portability of the non-standard external seq utility.
For small input numbers, this is probably also faster than using seq, and also gives you the flexibility to act on each number individually.
For large input numbers, seq will be faster.
#!/usr/bin/env bash
read -p "Input number: " k
for (( i = k; i >= 1; --i )); do
echo "$i"
done
You could avoid the newline (and maybe add a :) after asking for user input, so that it's clear that the first 5 is user input.
Then here is an example that uses seq instead of while, though according to #andlrc, seq is less portable.
#!/bin/bash
#Print decremental numbers based on user input
#n...8 7 6 5 4 3 2 1
printf "Input number: "
read k
seq $k -1 1
Output:
$ ./decr.sh
Input number: 5
5
4
3
2
1
Edit:
#mklement0's answer is best.
read -p "Input number: " k
The read -s will turn off the echo effect of read
#!/bin/bash
#Print decremental numbers based on user input
#n...8 7 6 5 4 3 2 1
echo "Input number"
read -s k
while test $k != 0
do
echo "$k"
k="$(( k - 1 ))"
done
Your formatting is broken:
echo "Input number"; read k; while test $k != 0; do
echo "$k"; k="$(( k - 1 ))"; done
Seems to work fine for me:
Input number
3 <- this is what you enter
3 <- this is the beginning of the loop
2
1
try
#!/bin/bash
#Print decremental numbers based on user input
#n...8 7 6 5 4 3 2 1
echo "Input number"
read k
seq $((k-1)) -1 1
alternatively
#!/bin/bash
#Print decremental numbers based on user input
#n...8 7 6 5 4 3 2 1
echo "Input number"
read k
while test $k -gt 1; do
k=$((k-1))
echo "$k"
done
Using for and seq is the most elegant:
for i in `seq 5 -1 1`; do
echo $i
done
Output:
5
4
3
2
1

Column Read and Average (BASH)

so I'm dealing with column averaging problems
I have several columns of number,
1 2 3 4 5
2 3 4 5 6
8 4 5 6 7
9 8 7 6 5
1 9 9 9 2
What I want to do is to take an average of each column so that they will yield
4 5 5 6 5
The summing part is not a problem, I'm able to get the sum of each column. However, somehow I'm having trouble in counting the number of columns (variable $x)
Here is my code
while read line; do
x = 0
read -a array <<< "$line"
for i in "${!array[#]}"
do
column[${i}]=$((${column[${i}]}+${array[$i]}))
((x++))
done
done < $TMP
for sum in ${column[#]}
do
average=`expr $sum / $x`
remainder=`expr $sum % $x`
mult=`expr $remainder \* 10`
fracvalue=`expr $mult / $x`
echo $x
echo $average
echo $sum
echo $remainder
echo $mult
echo $fracvalue
done
The last several lines are for my testing purposes, here the $X keeps showing 1 instead of 5. That's why it screws up all the other variable. Does anybody know where's the flaw in this code? Really appreciate your help. Thanks
Another approach:
#!/bin/bash
declare -ia sum # declare array of integers
declare -i lines=0 # declare integer to count lines
# read file to array and sum values
while read -ra array; do
for ((i=0; i<${#array[#]};i++)); do
sum[$i]+=${array[$i]}
done
lines+=1
done < file
# print averages
for ((j=0; j<$i;j++)); do
echo -n "$((${sum[$j]}/$lines)) "
done
Output:
4 5 5 6 5
The problem is in the line
x = 0
should be
x=0 and make ((x+1)) as let "x+=1" it should work.
Maybe your file doesn't have the same number of lines in each column.
while read line; do
read -a array <<< "$line"
for i in "${!array[#]}"
do
column[${i}]=$((${column[${i}]}+${array[$i]}))
((x[${i}]++))
done
done < $TMP
for i in ${!column[#]}
do
sum=${column[$i]}
x=${x[${i}]}
#your calcul
done
The initial issue, aside from a few syntax peculiarities, was the resetting of x on each read which caused you to lose your line count. Additionally, your array is an indexed array, not an associative array.
Making the small adjustments and declareing the variables at the beginning (to hint to bash they are integers or arrays), it works as planned. note: there is no need for a here-string to assign the array, just use regular array syntax. Also note, for indexed arrays, there is no need to dereference the variable within [ ], simply ${array[i]} is fine.
While the use of expr is fine (old, slow, but portable and fine), you could use bash arithmetic syntax for your parameter calculations at the end. (you already have arrays). Finally, you are better served initializing each column[$i]=0 on the first read loop. You can do that with a simple flag:
#!/bin/bash
declare -i ncols=0
declare -i x=0
declare -a column
while read -r line; do
array=( $line )
[ $ncols -eq 0 ] && ncols=${#array[#]}
for ((i = 0; i < ncols; i++))
do
[ $x -eq 0 ] && column[$i]=0
column[$i]=$(( ${column[i]} + ${array[i]}))
done
((x++))
done < "$1"
for sum in ${column[#]}
do
average=`expr $sum / $x`
remainder=`expr $sum % $x`
mult=`expr $remainder \* 10`
fracvalue=`expr $mult / $x`
echo $x
echo $average
echo $sum
echo $remainder
echo $mult
echo $fracvalue
done
exit 0
Output
$ bash colavg.sh dat/colavg.txt
5
4
21
1
10
2
5
5
26
1
10
2
5
5
28
3
30
6
5
6
30
0
0
0
5
5
25
0
0
0

put rows of numbers into a column with shell script

I have a file with many lines, like
1 jfkdajfd 1 2 3 5
2 fkldfjld
3 fdkfloaf 9 10
4 fldfldkf
5 fdskf;ak 12 1 4
I want to get all the numbers and put them in a column in a file, like
1
2
3
5
9
10
12
1
4
how can I achieve this?
thanks
In your case, it looks like you can do this:
awk '{for (i=3;i<=NF;++i) {print $i}}'
This is assuming that all the numbers you want to print occur in column 3 or after.
cat file | while read line
do
for i in $(echo ${line})
do
isnumeric=$(echo ${i} | grep -q [0-9]; echo ${?})
if [ ${isnumeric} -eq 0 ]
then
echo ${i} >> outfile
fi
done
done
not bulletproof and not as elegant as the previously given solutions, but it shows what is being used for determining if this is a numeric or not.
while read num alpha rest; do
[[ "$rest" ]] && printf "%s\n" $rest # <-- variable is unquoted
done < filename

Resources