I have been using a pretty basic, and for the most part straight forward, method to converting base-10 numbers {1..256} to base-4 or quaternary numbers. I have been using simple division $(($NUM/4)) to get the main result in order to get the remainders $(($NUM%4)) and then printing the remainders in reverse to arrive at the result. I use the following bash script to do this:
#!/bin/bash
NUM="$1"
main() {
local EXP1=$(($NUM/4))
local REM1=$(($NUM%4))
local EXP2=$(($EXP1/4))
local REM2=$(($EXP1%4))
local EXP3=$(($EXP2/4))
local REM3=$(($EXP2%4))
local EXP4=$(($EXP3/4))
local REM4=$(($EXP3%4))
echo "
$EXP1 remainder $REM1
$EXP2 remainder $REM2
$EXP3 remainder $REM3
$EXP4 remainder $REM4
Answer: $REM4$REM3$REM2$REM1
"
}
main
This script works fine for numbers 0-255 or 1-256. But beyond this(these) ranges, results become mixed and often repeated or inaccurate. This isn't so much of a problem as I don't intend to convert numbers beyond 256 or less than 0 (negative numbers [yet]).
My question is: "Is there a more simplified method to do this, possibly using expr or bc?
Base 4 conversion in bash
int2b4() {
local val out num ret=\\n;
for ((val=$1;val;val/=4)){
out=$((val%4))$out;
}
printf ${2+-v} $2 %s${ret[${2+1}]} $out
}
Invoked with only 1 argument, this will convert to base 4 and print the result followed by a newline. If a second argument is present, a variable of this name will be populated, no printing.
int2b4 135
2013
int2b4 12345678
233012011032
int2b4 5432 var
echo $var
1110320
Detailled explanation:
The main part is (could be written):
out=""
for (( val=$1 ; val > 0 ; val = val / 4 )) ;do
out="$((val%4))$out"
done
We're conversion loop could be easily understood (i hope)
local ensure out val num to be local empty variables and initialise locally ret='\n'
printf line use some bashisms
${2+-v} is emppty if $2 is empty and represent -v if not.
${ret[${2+1}]} become respectively ${ret[]} ( or ${ret[0]} ) and ${ret[1]}
So this line become
printf "%s\n" $out
if no second argument ($2) and
printf -v var "%s" $out
if second argument is var (Note that no newline will be appended to a populated variable, but added for terminal printing).
Conversion back to decimal:
There is a bashism letting you compute with arbitrary base, under bash:
echo $((4#$var))
5432
echo $((4#1110320))
5432
In a script:
for integer in {1234..1248};do
int2b4 $integer quaternary
backint=$((4#$quaternary))
echo $integer $quaternary $backint
done
1234 103102 1234
1235 103103 1235
1236 103110 1236
1237 103111 1237
1238 103112 1238
1239 103113 1239
1240 103120 1240
1241 103121 1241
1242 103122 1242
1243 103123 1243
1244 103130 1244
1245 103131 1245
1246 103132 1246
1247 103133 1247
1248 103200 1248
Create a look-up table taking advantage of brace expansion
$ echo {a..c}
a b c
$ echo {a..c}{r..s}
ar as br bs cr cs
$ echo {0..3}{0..3}
00 01 02 03 10 11 12 13 20 21 22 23 30 31 32 33
and so, for 0-255 in decimal to base-4
$ base4=({0..3}{0..3}{0..3}{0..3})
$ echo "${base4[34]}"
0202
$ echo "${base4[255]}"
3333
Related
I'm encountering a problem with creating a Bash completion function, when the command is expected to contain colons. When you type a command and press tab, Bash inserts the contents of the command line into an array, only these arrays are broken up by colons. So the command:
dummy foo:apple
Will become:
('dummy' 'foo' ':' 'apple')
I'm aware that one solution is to change COMP_WORDBREAKS, but this isn't an option as it's a team environment, where I could potentially break other code by messing with COMP_WORDBREAKS.
Then this answer suggests using the _get_comp_words_by_ref and __ltrim_colon_completions variables, but it is not remotely clear to me from the answer how to use these.
So I've tried a different solution below. Basically, read the command line as a string, and figure out which word the user's cursor is currently selecting by calculating an "offset". If there is a colon in the command line with text to the left or right of it, it will add 1 each to the offset, and then subtract this from the COMP_CWORD variable.
1 #!/bin/bash
2 _comp() {
3 #set -xv
4 local a=("${COMP_WORDS[#]}")
5 local index=`expr $COMP_CWORD`
6 local c_line="$COMP_LINE"
7
8 # Work out the offset to change the cursor by
9 # This is needed to compensate for colon completions
10 # Because COMP_WORDS splits words containing colons
11 # E.g. 'foo:bar' becomes 'foo' ':' 'bar'.
12
13 # First delete anything in the command to the right of the cursor
14 # We only need from the cursor backwards to work out the offset.
15 for ((i = ${#a[#]}-1 ; i > $index ; i--));
16 do
17 regex="*([[:space:]])"${a[$i]}"*([[:space:]])"
18 c_line="${c_line%$regex}"
19 done
20
21 # Count instances of text adjacent to colons, add that to offset.
22 # E.g. for foo:bar:baz, offset is 4 (bar is counted twice.)
23 # Explanation: foo:bar:baz foo
24 # 0 12 34 5 <-- Standard Bash behaviour
25 # 0 1 <-- Desired Bash behaviour
26 # To create the desired behaviour we subtract offset from cursor index.
27 left=$( echo $c_line | grep -o "[[:alnum:]]:" | wc -l )
28 right=$( echo $c_line | grep -o ":[[:alnum:]]" | wc -l )
29 offset=`expr $left + $right`
30 index=`expr $COMP_CWORD - $offset`
31
32 # We use COMP_LINE (not COMP_WORDS) to get an array of space-separated
33 # words in the command because it will treat foo:bar as one string.
34 local comp_words=($COMP_LINE)
35
36 # If current word is a space, add an empty element to array
37 if [ "${COMP_WORDS[$COMP_CWORD]}" == "" ]; then
38 comp_words=("${comp_words[#]:0:$index}" "" "${comp_words[#]:$index}" )
39 fi
40
41
42 local cur=${comp_words[$index]}
43
44 local arr=(foo:apple foo:banana foo:mango pineapple)
45 COMPREPLY=()
46 COMPREPLY=($(compgen -W "${arr[*]}" -- $cur))
47 #set +xv
48 }
49
50 complete -F _comp dummy
Problem is, this still doesn't work correctly. If I type:
dummy pine<TAB>
Then it will correctly complete dummy pineapple. If I type:
dummy fo<TAB>
Then it will show the three available options, foo:apple foo:banana foo:mango. So far so good. But if I type:
dummy foo:<TAB>
Then the output I get is dummy foo:foo: And then further tabs don't work, because it interprets foo:foo: as a cur, which doesn't match any completion.
When I test the compgen command on its own, like so:
compgen -W 'foo:apple foo:banana foo:mango pineapple' -- foo:
Then it will return the three matching results:
foo:apple
foo:banana
foo:mango
So what I assume is happening is that the Bash completion sees that it has an empty string and three available candidates for completion, so adds the prefix foo: to the end of the command line - even though foo: is already the cursor to be completed.
What I don't understand is how to fix this. When colons aren't involved, this works fine - "pine" will always complete to pineapple. If I go and change the array to add a few more options:
local arr=(foo:apple foo:banana foo:mango pineapple pinecone pinetree)
COMPREPLY=()
COMPREPLY=($(compgen -W "${arr[*]}" -- $cur))
Then when I type dummy pine<TAB> it still happily shows me pineapple pinecone pinetree, and doesn't try to add a superfluous pine on the end.
Is there any fix for this behaviour?
One approach that's worked for me in the past is to wrap the output of compgen in single quotes, e.g.:
__foo_completions() {
COMPREPLY=($(compgen -W "$(echo -e 'pine:cone\npine:apple\npine:tree')" -- "${COMP_WORDS[1]}" \
| awk '{ print "'\''"$0"'\''" }'))
}
foo() {
echo "arg is $1"
}
complete -F __foo_completions foo
Then:
$ foo <TAB>
$ foo 'pine:<TAB>
'pine:apple' 'pine:cone' 'pine:tree'
$ foo 'pine:a<TAB>
$ foo 'pine:apple'<RET>
arg is pine:apple
$ foo pi<TAB>
$ foo 'pine:
I'm researching the rhythmic elements of prime number sequences in binary. I have multiple sets of files containing vertical lists, and I want to apply bitwise logic operators between any two of them line-by-line.
i.e.
$cat binary-superprimes.txt
11
101
1011
10001
11111
101001
111011
1000011
1010011
1101101
$cat binary-perniciousprimes.txt
11
101
111
1011
1101
10001
10011
11111
100101
101001
I'm looking for commands, a script, or an application (I'd prefer commands/a script but its not deal-breaking) that will let me and/or/xor/etc. these outputs in order line-by-line, in much the same style/similar to the output of diff or comm.
Using CentOS 7/Ubuntu 18.04/MacOS 10.15.
edit
Expected output (binary expansion of XORing each entry above in decimal):
0
0
1100
11010
10010
111000
101000
1011100
1110110
1000100
As for what I've tried, as I said I've played around with for loops, but I don't know how (or if its possible) two iterate two lists for comparison in this context (i.e. two "for i in"'s with a single "done" - using $i and $x as inputs for a basic "echo (($x^$i))"
I've also tried a program called "bitwise" but its output is too verbose and it cannot seem to read files, only values.
Assuming your bash version is >= 4.0 and supports mapfile,
would you try the following:
mapfile -t x < "binary-superprimes.txt"
mapfile -t y < "binary-perniciousprimes.txt"
for (( i=0; i<${#x[#]}; i++ )); do
echo "obase=2;" $(( 2#${x[i]} ^ 2#${y[i]} )) | bc
done
Output:
0
0
1100
11010
10010
111000
101000
1011100
1110110
1000100
In case your bash does not support mapfile command, please try the alternative:
while read -r line; do
x+=($line)
done < "binary-superprimes.txt"
while read -r line; do
y+=($line)
done < "binary-perniciousprimes.txt"
for (( i=0; i<${#x[#]}; i++ )); do
echo "obase=2;" $(( 2#${x[i]} ^ 2#${y[i]} )) | bc
done
Hope this helps.
You can use bc for this purpose. First you create file
xor.bc
define xor(x,y) {
auto n,z,t,a,b,c,os,qx,qy;
os=scale;scale=0
n=0;x/=1;y/=1
if(x<0){x=-1-x;n=!n}
if(y<0){y=-1-y;n=!n}
z=0;t=1;while(x||y){
qx=x/4;qy=y/4;
c=(a=x-4*qx)+(b=y-4*qy)
if(!c%2)c=a+4-b
z+=t*(c%4)
t*=4;x=qx;y=qy
}
if(n)z=-1-z
scale=os;return (z)
}
Then you create loop to get the numbers one by one. And you can exec XOR by this:
paste binary-superprimes.txt binary-perniciousprimes.txt |while read var1 var2;
do
echo "ibase=2;obase=2;xor($var1;$var)|bc -l xor.bc
done
I'm doing for fun and this as part of my learning process in Shell scripting.
Let say I have initial input A B C
What I'm trying to do is to split the string and convert each of them to decimal value.
A B C = 65 66 67
Then I'll add the decimal value to random number, let say number 1.
Now, decimal value will become = 66 67 68
Finally, I'll convert the decimal to the original value again which will become B C D
ubuntu#Ubuntu:~$ cat testscript.sh -n
#!/bin/bash
1 string="ABC"
2
3 echo -e "\nSTRING = $string"
4 echo LENGTH = ${#string}
5
6 # TUKAR STRING KE ARRAY ... word[x]
7 for i in $(seq 0 ${#string})
8 do word[$i]=${string:$i:1}
9 done
10
11 echo -e "\nZero element of array is [ ${word[0]} ]"
12 echo -e "Entire array is [ ${word[#]}] \n"
13
14 # CHAR to DECIMAL
15 for i in $(seq 0 ${#string})
16 do
17 echo -n ${word[$i]}
18 echo -n ${word[$i]} | od -An -tuC
19 chardec[$i]=$(echo -n ${word[$i]} | od -An -tuC)
20 done
21
22 echo -e "\nNEXT, DECIMAL VALUE PLUS ONE"
23 for i in $(seq 0 ${#string})
24 do
25 echo `expr ${chardec[$i]} + 1`
26 done
27
28 echo
This is the output
ubuntu#Ubuntu:~$ ./testscript.sh
STRING = ABC
LENGTH = 3
Zero element of array is [ A ]
Entire array is [ A B C ]
A 65
B 66
C 67
NEXT, DECIMAL VALUE PLUS ONE
66
67
68
1
As you can see in the output, there are 2 problems (or maybe more)
The last for loop processing additional number. Any idea how to fix this?
NEXT, DECIMAL VALUE PLUS ONE
66
67
68
1
This is the formula to convert decimal value to char. I'm trying to put the last value to another array and then put it in another loop for this purpose. However, I'm still have no idea how to do this in loop based on previous data.
ubuntu#Ubuntu:~$ printf "\x$(printf %x 65)\n"
A
Please advise
Using bash you can replace all of your code with this code:
for i; do
printf "\x"$(($(printf '%x' "'$i'") +1))" "
done
echo
When you run it as:
./testscript.sh P Q R S
It will print:
Q R S T
awk to the rescue!
simpler to do the same in awk environment.
$ echo "A B C" |
awk 'BEGIN{for(i=33;i<127;i++) o[sprintf("%c",i)]=i}
{for(i=1;i<=NF;i++) printf "%c%s", o[$i]+1, ((i==NF)?ORS:OFS)}'
B C D
seq is from FIRST to LAST, so if your string length is 3, then seq 0 3 will give you <0,1,2,3>. Your second to last loop (lines 16-20) is actually running four iterations, but the last iteration prints nothing.
To printf the ascii code, insert it inline, like
printf "\x$(printf %x `expr ${chardec[$i]} + 1`) "
or more readably:
dec=`expr ${chardec[$i]} + 1`
printf "\x$(printf %x $dec)\n"
I have a file which contains following 10 numbers:
> cat numbers
9
11
32
88
89
90
95
104
118
120
>
I would like to print out the preceding number only if it is at least 5 numbers smaller than the current number. So I expect output like this:
11
32
90
95
104
120
I have a script which does this:
> cat test.sh
#!/bin/bash
subtraction="5"
while read -r number; do
if [ -n "$previous_number" ] && (( $((number - subtraction)) >= previous_number )); then
echo "$previous_number"
fi
previous_number="$number"
done < "$1"
> ./test.sh numbers
11
32
90
95
104
>
However, it doesn't print 120. What is the most elegant/proper solution in such cases? Should I simply add tail -1 "$1" after the while loop?
For someone else reading this for whom while read genuinely is not iterating over the last line of a file, there's a likely different problem: An input file without a trailing newline.
For that, one can amend their code as follows:
while read -r number || [[ $number ]]; do
: "...logic here..."
done
This is true because without a trailing newline, read will return false, and so the body of the loop will not be executed with the original code, but $number is still populated.
However, for this specific program and its specific input given, there's nothing at all wrong with how the while read idiom handles the last line of an input; the output at hand follows from the program's logic as written and defined.
Consider the following version, which makes what's happening more clear:
#!/bin/bash
subtraction="5"
while read -r number; do
if [[ $previous_number ]] && (( (number - subtraction) >= previous_number )); then
printf '%q is at least %q away from %q\n' "$previous_number" "$subtraction" "$number"
else
printf '%q is not %q away from %q\n' "$previous_number" "$subtraction" "$number"
fi
previous_number="$number"
done <"$1"
Its output is:
'' is not 5 away from 9
9 is not 5 away from 11
11 is at least 5 away from 32
32 is at least 5 away from 88
88 is not 5 away from 89
89 is not 5 away from 90
90 is at least 5 away from 95
95 is at least 5 away from 104
104 is at least 5 away from 118
118 is not 5 away from 120
...as this last line of output shows, it is genuinely considering 120, and deciding not to print it per your program's logic as defined.
It is easier to use awk for this job:
awk 'NR>1 && $1-p>=5{print p} {p=$1}' file
Output:
11
32
90
95
104
btw 120 won't be printed in output because preceding number is 118 which is not <=5 to 120.
How to clean a list of points in a variable regarding on if it is
the same point or
a close by point (+-5).
Example each line is one point with to coordinates:
points="808,112\n807,113\n809,113\n155,183\n832,572"
echo "$points"
#808,112
#807,113
#809,113
#155,183
#832,572
#196,652
I would like to ignore points within a range of +-5 counts. The result should be:
echo "$points_clean"
#808,112
#155,183
#832,572
#196,652
I thought about looping through the list, but I need help to how to check if point coordinates already exist in the new list:
points_clean=$(for point in $points; do
x=$(echo "$point" | cut -d, -f1)
y=$(echo "$point" | cut -d, -f2)
# check if same or similar point coordinates already in $points_clean
echo "$x,$y"
done)
This seems to work with Bash 4.x (support for process substitution is needed):
#!/bin/bash
close=100
points="808,112\n807,113\n809,113\n155,183\n832,572"
echo -e "$points"
clean=()
distance()
{
echo $(( ($1 - $3) * ($1 - $3) + ($2 - $4) * ($2 - $4) ))
}
while read x1 y1
do
ok=1
for point in "${clean[#]}"
do
echo "compare $x1 $y1 with $point"
set -- $point
if [[ $(distance $x1 $y1 $1 $2) -le $close ]]
then
ok=0
break
fi
done
if [ $ok = 1 ]
then clean+=("$x1 $y1")
fi
done < <( echo -e "$points" | tr ',' ' ' | sort -u )
echo "Clean:"
printf "%s\n" "${clean[#]}" | tr ' ' ','
The sort is optional and may slow things down. Identical points will be too close together, so the second instance of a given coordinate will be eliminated even if the first wasn't.
Sample output:
808,112
807,113
809,113
155,183
832,572
compare 807 113 with 155 183
compare 808 112 with 155 183
compare 808 112 with 807 113
compare 809 113 with 155 183
compare 809 113 with 807 113
compare 832 572 with 155 183
compare 832 572 with 807 113
Clean:
155,183
807,113
832,572
The workaround for Bash 3.x (as found on Mac OS X 10.10.4, for example) is a tad painful; you need to send the output of the echo | tr | sort command to a file, then redirect the input of the pair of loops from that file (and clean up afterwards). Or you can put the pair of loops and the code that follows (the echo of the clean array) inside the scope of { …; } command grouping.
In response to the question 'what defines close?', wittich commented:
Let's say ±5 counts. Eg. 808(±5,) 112(±5). That's why the second and third point would be "cleaned".
OK. One way of looking at that would be to adjust the close value to 50 in my script (allowing a difference of 52 + 52), but that rejects points connected by a line of length just over 7, though. You could revise the distance function to do ±5; it takes a bit more work and maybe an auxilliary abs function, or you could return the square of the larger delta and compare that with 25 (52 of course). You can play with what the criterion should be to your hearts content.
Note that Bash shell arithmetic is integer arithmetic (only); you need Korn shell (ksh) or Z shell (zsh) to get real arithmetic in the shell, or you need to use bc or some other calculator.