Bigger input base for bc? - radix

I'm searching for a calculator in base 31, i.e., I want both input and output to be in base 31. (I actually don't care much about the format of digits, only that it would be easy writable. So, for example `013456789ABCDEFGHIJKLMNOPQRSTU' would be fine.)
bc seems to be good option for small bases as you can define ibase=x; obase=x. But there is an upper limit for ibase — that is 16 as it doesn't want to use bigger digits than F.
Is there some workaround for that? Or, maybe a way how to recompile bc in a way it would support bigger bases? Or, alternatively is there some alternative to bc which would do at least basic arithmetic (like +, -, /, *, ^)?

POSIX bc won't bases higher than 16, but GNU bc will. From the manual,
A simple expression is just a constant. `bc' converts constants into
internal decimal numbers using the current input base, specified by the
variable IBASE. (There is an exception in functions.) The legal values
of IBASE are 2 through 16. Assigning a value outside this range to
IBASE will result in a value of 2 or 16. Input numbers may contain the
characters 0-9 and A-F. (Note: They must be capitals. Lower case
letters are variable names.) Single digit numbers always have the
value of the digit regardless of the value of IBASE. (i.e. A = 10.)
For multi-digit numbers, `bc' changes all input digits greater or equal
to IBASE to the value of IBASE-1. This makes the number `FFF' always
be the largest 3 digit number of the input base.

dc is another solution. Originally bc was implemented using dc although nowadays the two are separated. The only thing is that dc is more difficult to use
$ echo 31o 16i DEADBEEFp | dc
04 06 15 09 18 22 15
The only thing with it is that large digits are displayed as values instead of letters and you'll need to map that to characters if you want
$ echo 31o 16i DEADBEEFp | dc | awk -v RS=" " -v FS="" '{ if (NR > 1) { if (int($0) < 10) { printf int($0) } else { printf "%c", ($0 - 10 + 0x61) } } } END { printf "\n" }'
46f9imf
Compare with Wolfram Alpha:
Some more examples:
$ echo "31o 31 p" | dc
01 00
$ echo "31o 31 31 * p" | dc
01 00 00
$ echo "31o 31 31 31 * * p" | dc
01 00 00 00

If you just want to do some calculations and not heavy number crunching you can use my calculator Kalkulon together with a simple Kalkulon script for big number arithmetic. An interactive Kalkulon session looks like this:
Load("examples/bignum.k")
outl[1] = 0
string2Bn("1234567890ABCDEFGHIJKLMNOPQRSTU",31)
outl[2] = {587, 938220, 58035, 844882, 441786, 886253, 755598, 800665}
string2Bn("UTSR",31)
outl[3] = {922494}
mulBn(outl[2], outl[3])
outl[4] = {542, 369480, 374217, 718688, 983253, 751847, 772017, 360020, 658510}
printBn(out)
542369480374217718688983253751847772017360020658510
outl[5] = 51
Bn2string(outl[4],31)
outl[6] = "121UOIC5UEHLRAUOIC5UOIC5UOIC5UNHB64"
divBn(outl[4], outl[2])
outl[7] = {922494}
Bn2string(out,31)
outl[8] = "UTSR"
I am just curious: why do you want to calculate in base 31?

You can use Perl, with either the Math::BaseCalc module
$ echo $(( 31*31*31 )) | perl -n -MMath::BaseCalc -e 'my $calc = new Math::BaseCalc(digits => [ "0".."9", "a".."u" ]); print $calc->to_base($_) . "\n"'
1000
$ echo 0xDEADBEEF | perl -n -MMath::BaseCalc -e 'my $calc = new Math::BaseCalc(digits => [ "0".."9", "a".."u" ]); print $calc->to_base($_) . "\n"'
46f9imf
or the Math::Base::Convert module
$ echo $(( 31*31*31*31 )) | perl -n -MMath::Base::Convert -e '$f = new Math::Base::Convert("10", [ "0".."9", "a".."u" ]); print $f->cnv(int($_)) . "\n"'
10000
$ perl -MMath::Base::Convert -e '$f = new Math::Base::Convert("10", [ "0".."9", "a".."u" ]); print $f->cnv(31*31*31) . "\n"'
1000
$ perl -MMath::Base::Convert -e '$f = new Math::Base::Convert("10", [ "0".."9", "a".."u" ]); print $f->cnv(0xDEADBEEF) . "\n"'
46f9imf
In both cases [ "0".."9", "a".."u" ] is the digit list to use, which contains 31 symbols for base 31

Related

Print specific lines that have two or more occurrences of a particular character

I have file with some text lines. I need to print lines 3-7 and 11 if it has two "b". I did
sed -n '/b\{2,\}/p' file but it printed lines where "b" occurs two times in a row
You can use
sed -n '3,7{/b[^b]*b/p};11{/b[^b]*b/p}' file
## that is equal to
sed -n '3,7{/b[^b]*b/p};11{//p}' file
Note that b[^b]*b matches b, then any zero or more chars other than b and then a b. The //p in the second part matches the most recent pattern , i.e. it matches the same b[^b]*b regex.
Note you might also use b.*b regex if you want, but the bracket expressions tend to word faster.
See an online demo, tested with sed (GNU sed) 4.7:
s='11bb1
b222b
b n b
ww
ee
bb
rrr
fff
999
10
11 b nnnn bb
www12'
sed -ne '3,7{/b[^b]*b/p};11{/b[^b]*b/p}' <<< "$s"
Output:
b n b
bb
11 b nnnn bb
Only lines 3, 6 and 11 are returned.
Just use awk for simplicity, clarity, portability, maintainability, etc. Using any awk in any shell on every Unix box:
awk '( (3<=NR && NR<=7) || (NR==11) ) && ( gsub(/b/,"&") >= 2 )' file
Notice how if you need to change a range, add a range, add other line numbers, change how many bs there are, add other chars and/or strings to match, add some completely different condition, etc. it's all absolutely clear and trivial.
For example, want to print the line if there's exactly either 13 or 27 bs instead of 2 or more:?
awk '( (3<=NR && NR<=7) || (NR==11) ) && ( gsub(/b/,"&") ~ /^(13|27)$/ )' file
Want to print the line if the line number is between 23 and 59 but isn't 34?
awk '( 23<=NR && NR<=59 && NR!=34 ) && ( gsub(/b/,"&") >= 2 )' file
Try making similar changes to a sed script. I'm not saying you can't force it to happen, but it's not nearly as trivial, clear, portable, etc. as it is using awk.

How do I capture first tuesday in a month with zero padded in Unix

#Unix
I am trying to capture first tuesday of every month into a variable and trying to pad Zero against it without luck.
Below is the piece of code I was trying:
cal | sed -e 's/ \([1-9]\) /0\1 /g' -e 's/ \([1-9]\)$/0\1/' | awk 'NR>2{Sfields=7-NF; if (Sfields == 0 ) {printf "%d\n",$3;exit}}'
Can someone help me what I am missing here?
This awk should do:
cal | awk 'NR>2 && NF>4 {printf "%02d\n",$(NF-4);exit}'
03
To confirm its working:
for i in {1..12}; do cal -m $i | awk 'NR>2 && NF>4 {printf "%02d\n",$(NF-4);exit}' ; done
06
03
03
07
05
02
07
04
01
06
03
01
Or you can use ncal
ncal | awk '/Tu/ {printf "%02d\n",$2}'
03
If you like a version where you can specify name of week,
and would work if Monday is first day of week, then this gnu awk should do:
cal | awk 'NR==2 {for (i=1;i<=NF;i++) {sub(/ /,"",$i);a[$i]=i}} NR>2 {if ($a["Tu"]~/[0-9]/) {printf "%02d\n",$a["Tu"];exit}}' FIELDWIDTHS="3 3 3 3 3 3 3 3"
03
It uses FIELDWITH to make sure empty columns in start of month does not changes the output.
# for monday calendar
cal -m1 | sed -n '1,2b;/^.\{3\} \{0,1\}\([0-9]\{1,2\}\) .*/ {s//0\1/;s/.*\([0-9]\{2\}\)$/\1/p;q;}'
# for sunday calendar
cal -s1 01 01 2015 | sed -n '1,2b;/^.\{6\} \{0,1\}\([0-9]\{1,2\}\) .*/ {s//0\1/;s/.*\([0-9]\{2\}\)$/\1/p;q;}'
cal option depend on system (tested here on Red Hat 6.6) and mean -m for monday as first day and -sfor sunday (the attached 1 is for 1 month display). Take the line according to your specified output of cal.
don't print line by default
don't care of line 1 and 2
take line with non empty second(/third) group
take second(/third) group (position) of number until next one and replace by a 0, remove trailng char
take the 2 last digit of first group, remove the rest and print it
quit (no other line)
thanks to #Jotne for all remark about first wanted day in second week (4th line and not 3th) and first day of the week
I think I got the answer.
cal | awk 'NR>2{Sfields=7-NF; if (Sfields == 0 ) {printf "%02d\n",$3;exit}}'
Above statement would do."%02d" does it for me
bash and date. May be slower than parsing cal:
y=2015
for m in {1..12}; do
for d in {01..07}; do
if [[ $(date -d "$y-$m-$d" +%w) -eq 2 ]]; then
echo $d
break
fi
done
done
Translating into awk: will be faster as it doesn't have to call date multiple times:
gawk -v y=2015 '
BEGIN {
for (m=1; m<=12; m++) {
for (d=1; d<=7; d++) {
t = mktime( y " " m " " d " 12 0 0" )
if (strftime("%w", t) == 2) {
printf "%02d\n", d
break
}
}
}
}
'

Print log records within a time interval using sed

I am using sed to print log records within a time interval. My time format is YYYY-MM-DD HH:MM:ss,sss, for example "2014-07-22 15:33:25,758". I tried
sed -n '/2014-07-23 01:00:00,000/,/2014-07-23 02:00:00,000/ p'
But it does not work. I can find the solution for YYYY-MM-DD, but it is not for my case. Can anyone help?
You can replace insignificant digits with . so it would match anything within the range:
sed -n '/2014-07-23 01:..:..,.../,/2014-07-23 02:..:..,.../p'
And perhaps just remove them totally:
sed -n '/2014-07-23 01:/,/2014-07-23 02:/p'
Ultimately, sed is not the best tool for this job; it does exact matches rather than range-based matches (within the exactness of regular expressions).
Using awk, you can do range-based checking:
awk '$1 == "2014-07-23" && $2 >= "01:00:00" && $2 < "02:00:00" { print }'
where the { print } is optional but explicit. One of the advantages of the ISO 8601 date and time notations is precisely that lexicographic order is the same as time order.
For your sed command to work, those exact times must appear in the file. konsolebox has a good solution that should work in your case (but see Jonathan Leffler's comment there, and also his awk solution which is simpler than mine).
In general you need something more powerful than sed, like awk. In the example below, note how you must specify the input times (space-separated values, no decimals on seconds). Also note that it is gawk-specific. Also note that I've assumed the time is the first and second space-separated fields. Adjust as needed.
gawk -vstart="2014 07 23 01 00 00" -vend="2014 07 23 02 00 00" '
BEGIN {nstart=mktime(start); nend=mktime(end)}
{
t = $1 " " $2
gsub(/[-:]/, " ", t);
nt = mktime(substr(t, 1, 19))
if (nt >= nstart && nt <= nend)
print
}
' file

Adding two numbers in unix

I tried to add two numbers by below logic:
num=0001
newnum=`expr $num + 1`
echo $newnum
But it returns '2', my desired output is '0002'.
num=0001
newnum=`expr $num + 0001`
echo $newnum
I used above logic also,but no use. What is needed here to get my desired output.
Thanks in advance.
Use printf to print numbers with leading zeroes:
printf "%04d\n" $num
You shouldn't do arithmetic with numbers with leading zeroes, because many applications treat an initial zero as meaning that the number is octal, not decimal.
Use printf:
$ num=0001
$ printf "%04d" $(expr $num + 1)
0002
In order to assign the result to a variable, say:
$ newnum=$(printf "%04d" $(expr $num + 1))
$ echo $newnum
0002
Numbers with leading zeros are interpreted as octal numbers. In order to treat them as decimals you need to prepend 10# to the number. Finally, use printf to pad the number with zeros.
num=0001
newNum=$((10#$num + 1))
paddedNum=$(printf %04d $newNum)

Collect numerals at the beginning of the file

I have a text file which contains some numerals, for example,
There are 60 nuts and 35 apples,
but only 24 pears.
I want to collect these numerals (60, 35, 24) at the beginning of the same file, in particular, I want after processing, the file to read
read "60"
read "35"
read "24"
There are 60 nuts and 35 apples,
but only 24 pears.
How could I do this using one of the text manipulating tolls available in *nix?
You can script an ed session to edit the file in place:
{
echo 0a # insert text at the beginning of the file
grep -o '[0-9]\+' nums.txt | sed 's/.*/read "&"/'
echo ""
echo . # end insert mode
echo w # save
echo q # quit
} | ed nums.txt
More succinctly:
printf "%s\n" 0a "$(grep -o '[0-9]\+' nums.txt|sed 's/.*/read "&"/')" "" . w q | ed nums.txt
One way to do it is:
egrep -o [0-9]+ input | sed -re 's/([0-9]+)/read "\1"/' > /tmp/foo
cat input >> /tmp/foo
mv /tmp/foo input