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.
Related
So we have a test file that has a 3 digit number per line, several hundred lines of in the file.
I need to find a way to read from the file the number (or 3 digits that make up the number), add them together, and then determine if the resulting sum is odd or even. My current script is reading each line as a whole number, and I am missing the part where I am able to sum the digits...
while read number
do
echo $number
if [ $((number % 2)) -eq 0 ]; then
echo even
else
echo odd
fi
done < input.txt
Setup:
$ cat input.txt
123
456
789
Assuming the results need to be used in follow-on operations then one bash idea:
while read -r in
do
sum=$(( ${in:0:1} + ${in:1:1} + ${in:2:1} ))
result="odd"
[[ $((sum%2)) -eq 0 ]] && result="even"
echo "${in} : ${sum} : ${result}"
done < input.txt
This generates:
123 : 6 : even
456 : 15 : odd
789 : 24 : even
If the sole purpose is to generate the even/odd flag, and performance is an objective, you'll want to look at something other than bash, eg:
awk '
{ sum=0
for (i=1;i<=length($1);i++)
sum+=substr($1,i,1)
result=(sum%2 == 0 ? "even" : "odd")
printf "%s : %s : %s\n",$1,sum,result
}
' input.txt
This generates:
123 : 6 : even
456 : 15 : odd
789 : 24 : even
Something like this maybe.
#!/usr/bin/env bash
while IFS= read -r numbers; do
[[ $numbers =~ ^([[:digit:]]{1})([[:digit:]]{1})([[:digit:]]{1})$ ]] &&
arr=("${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}")
sum=$(IFS=+; printf '%s' "${arr[*]}")
if (( $((sum)) % 2 == 0)); then
printf '%s is %d %% 2 = 0 is even\n' "$sum" "$((sum))"
else
printf '%s is %d %% 2 = 0 is odd\n' "$sum" "$((sum))"
fi
done < file.txt
several hundred lines of in the file.
Bash is not the tool for this, It can be done with the shell but it will be very slow, and memory intensive, Use something like awk or perl or ...
Simply replace
$((number % 2))
with
$(( (number/100 + number/10%10 + number%10) % 2))
in your code. Assuming number consists of three decimal digits, number/100 extracts the most significant digit, number/10%10 extracts the digit in the middle, and number%10 extracts the least significant digit. Note that shell arithmetic is integer only, and the operators / and % have equal precedence and are left-associative.
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
Using a two-level for loop and seq works fine, and the code
for i in `seq 0 3`; do for j in `seq 0 3`; do echo $i $j; done; done
gives the expected output:
0 0
0 1
1 0
1 1
But if I want a more customised list of numbers:
for i in '-1 4.5'; do for j in '0 -2.2'; do echo $i $j; done; done
I get the output
-1 4.5 0 -2.2
Is there an easy way to do this?
The usage of single ticks is preventing the shell from tokenizing the list supplied to the for loop:
#!/bin/sh
for i in -1 4.5
do for j in 0 -2.2
do # -- keeps the highly portable printf utility from
# interpreting '-' as an argument
printf -- "$i" "$j\n"
done
done
var a = [1,2,3];
var b = [1,2,3];
for (i in a) {
for (j in b) {
alert (i + j);
}
}
I have also created a jsfiddle:
http://jsfiddle.net/f8k5tpqm/1/
I got the following problem:
Given 2 files of N numbers like
file1.dat: 1,2,3,4,5,6,7,8,9,0
file2.dat: 2,5,4,7,6,9,8,1,0,3
The file2.dat is actually just a shuffled version of the first one.
I want to know how many time the order of two consecutive numbers in the first file has changed in the second (that contain the same numbers). For exemple, in the file one we start looking for 1 and 2, in the second file 2 come before the 1, so there was a change of the order; in the first file there is 9 and then 0 and also in the second file this order is maintained because 9 come before 0.
The actual data that I'm using are roughly composed of 26000 numbers and they are all distinct.
I thought of somenthing like:
for(i=0; i<N-1; i++)
for(j=0; j<N; j++)
for(k=0 ; k<N; k++)
if(B[j]==A[i] && B[k]==A[i+1] && k < j )
count++
print("The number of inversion is: %d\n",count)
But I don't know how to write it in awk (I wrote a little C program but it takes more than 5 hours to give me the answer) and I don't know if something like could give me a result in a resonable time.
I hope your two files obey the certain rules:
same amount of distinct number,
both files have single line
I didn't do those format checking. see my solution:
awk -F, 'FNR==NR{n=NF;for(i=1;i<=NF;i++)o[i]=$i;next;}
{for(i=1;i<=NF;i++)v[$i]=i}
END{ for(i=1;i<=n-1;i++) t+=v[o[i]]>v[o[i+1]]?1:0;
print "inversions:",t;
}' file1 file2
test:
kent$ head file1 file2
==> file1 <==
1,2,3,4,5,6,7,8,9,0
==> file2 <==
2,5,4,7,6,9,8,1,0,3
kent$ awk -F, 'FNR==NR{n=NF;for(i=1;i<=NF;i++)o[i]=$i;next;}
{for(i=1;i<=NF;i++)v[$i]=i}
END{ for(i=1;i<=n-1;i++) t+=v[o[i]]>v[o[i+1]]?1:0;
print "inversions:",t;
}' file1 file2
inversions: 5
If you want to print some debug info, say, print inversion pairs as well, see this:
kent$ awk -F, 'FNR==NR{n=NF;for(i=1;i<=NF;i++)o[i]=$i;next;}
{for(i=1;i<=NF;i++)v[$i]=i}
END{ for(i=1;i<=n-1;i++) {
if(v[o[i]]>v[o[i+1]]){
print "inversion pair foud:"o[i],o[i+1]
t++;
}
}
print "inversions:",t;
}' file1 file2
inversion pair foud:1 2
inversion pair foud:3 4
inversion pair foud:4 5
inversion pair foud:6 7
inversion pair foud:8 9
inversions: 5
if you want some other information e.g. original index/order, changed order, they are also easy to be added.
hope it helps.
EDIT
if your data files are in single-column format. try this:
awk -F, 'FNR==NR{o[NR]=$0;next;}{v[$0]=FNR;n=FNR}
END{ for(i=1;i<=n-1;i++) t+=v[o[i]]>v[o[i+1]]?1:0;
print "invertions:",t;
}' file1 file2
test screencast. just for testing my just written recording script ;)
I know that you already have an accepted clean answer.
Just sharing:
sgeorge-mn:~ sgeorge$ cat stack.sh
VALUE1=$1
VALUE2=$2
for POS in `sed 's/,/ /g' file1.dat`
do
((COUNT++))
if [[ $VALUE1 == $POS ]] ; then
VAL1_POS=$COUNT
fi
if [[ $VALUE2 == $POS ]] ; then
VAL2_POS=$COUNT
fi
done
for MATCH in `sed 's/,/ /g' file2.dat`
do
((COUNT2++))
if [[ $VALUE1 == $MATCH ]] ; then
VAL1_POS2=$COUNT2
fi
if [[ $VALUE2 == $MATCH ]] ; then
VAL2_POS2=$COUNT2
fi
done
if [[ $VAL1_POS -gt $VAL2_POS ]] ; then
P1=1
fi
if [[ $VAL1_POS2 -gt $VAL2_POS2 ]] ; then
P2=1
fi
if [[ $VAL1_POS -lt $VAL2_POS ]] ; then
P1=2
fi
if [[ $VAL1_POS2 -lt $VAL2_POS2 ]] ; then
P2=2
fi
if [[ $VAL1_POS -eq $VAL2_POS ]] ; then
P1=3
fi
if [[ $VAL1_POS2 -eq $VAL2_POS2 ]] ; then
P2=3
fi
if [[ $P1 == $P2 ]]; then
echo "No order change"
else
echo "Order changed"
fi
How to execute the script:
I am assuming following:
Both file have exactly same numbers in same order.
You will not give non existing number (not existing in file*.dat) as input to the script
sgeorge-mn:~ sgeorge$ bash stack.sh 5 7
No order change
sgeorge-mn:~ sgeorge$ bash stack.sh 4 5
Order changed
sgeorge-mn:~ sgeorge$ bash stack.sh 9 0
No order change
sgeorge-mn:~ sgeorge$ bash stack.sh 1 2
Order changed
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