Do math operation on the numbers typed into command line without call bc
Shortcut Alt-c (bash)
With bash, using the readline utility, we can define a key sequence to place the word calc
at the start and enclose the text written so far into double quotes:
bind '"\ec": "\C-acalc \"\e[F\""'
Having executed that, you type 23 + 46 * 89
for example, then Alt-c to get:
calc "23 + 46 * 89"
Just press enter and the math will be executed by the function defined as calc, which could be as simple as, or a lot more complex:
calc () { <<<"$*" bc -l; }
a (+) Alias
We can define an alias:
alias +='calc #'
Which will comment the whole command line typed so far. You type:
+ (56 * 23 + 26) / 17
When you press enter, the line will be converted to calc #(56 * 23 + 26) / 17
and the command calc
will be called. If calc is this function:
bash
calc(){ s=$(HISTTIMEFORMAT='' history 1); # recover last command line.
s=${s#*[ ]}; # remove initial spaces.
s=${s#*[0-9]}; # remove history line number.
s=${s#*[ ]+}; # remove more spaces.
eval 'bc -l <<<"'"$s"'"'; # calculate the line.
}
ksh
calc(){ s=$(history -1 | # last command(s)
sed '$!d;s/^[ \t]*[0-9]*[ \t]*+ //'); # clean it up
# (assume one line commads)
eval 'bc -l <<<"'"$s"'"'; # Do the math.
}
zsh zsh doesn't allow neither a +
alias nor a #
character.
The value will be printed as:
$ + (56 * 23 + 26) / 17
77.29411764705882352941
Only a +
is required, String is quoted (no globs), shell variables accepted:
$ a=23
$ + (56 * 23 + $a) / 17
77.11764705882352941176
a (+) Function
With some limitations, this is the closest I got to your request with a function (in bash):
+() { bc -l <<< "$*"; }
Which will work like this:
$ + 25+68+8/24
93.33333333333333333333
The problem is that the shell parsing isn't avoided and a *
(for example) could get expanded to the list of files in the pwd.
If you write the command line without (white) spaces you will probably be ok.
Beware of writing things like $(...)
because they will get expanded.
The safe solution is to quote the string to be evaluated:
$ + '45 + (58+3 * l(23))/7'
54.62949752111249272462
$ + '4 * a(1) * 2'
6.28318530717958647688
Which is only two characters shorter that your _bc "6/2"
, but a +
seems more intuitive to me.
I use a variant of bash's magic alias hack:
asis() { bc <<< "$(history 1 | perl -pe 's/^ *[0-9]+ +[^ ]+ //')"; }
alias c='asis #'
Then:
$ c 1+1
2
$ c -10 + 20 / 5
-6
$ c (-10 + 20) / 5
2
$ c 2^8 / 13
19
$ c scale=5; 2^8 / 13
19.69230
The magic is the fact that alias expansion happens before the usual command line processing, which allows us to create a command whose remaining arguments follow a comment character, that the implementing function finds with the history command.
This magic allows me to type *
, (
, and other characters literally. But that also means I can't use shell variables because $
is also literal:
$ x=5.0
$ y=-1.2
$ z=4.7
$ c ($x + $y) > $z
(standard_in) 1: illegal character: $
(standard_in) 1: illegal character: $
(standard_in) 1: illegal character: $
I get around this by a bit of bootstrapping:
$ echo "x=$x; y=$y; z=$z"
x=5.0; y=-1.2; z=4.7
$ c x=5.0; y=-1.2; z=4.7; (x + y) > z
0
You might just be better off typing: bc
Enter 1 + 1 Enter Control+D
As a side note, I have my default bc
settings (like scale
) in $HOME/.bc
and I use bc -l
in the alias. Your use may not require these modifications.
In zsh
, you could do something like:
autoload zcalc
accept-line() {
if [[ $BUFFER =~ '^[ (]*[+-]? *(0[xX]|.)?[[:digit:]]+[^[:alnum:]]' ]]; then
echo
zcalc -e $BUFFER
print -rs -- $BUFFER
BUFFER=
fi
zle .$WIDGET
}
zle -N accept-line
It redefines the accept-line
widget (mapped on Enter) to a user-defined widget that checks if the current line starts with a number (decimal or hexadecimal) optionally prefixed with any number of (
s, looking for a non-alnum character after that to avoid false positives for commands like 7zip
or 411toppm
.
If that matches then we pass it to zcalc
(more useful than bc in that it can use shell variables and all of zsh math functions and number styles, but does not support arbitrary precision), add the line to history and accept an empty command.
Note that it can cause confusion if you enter a line with digits in things like:
cat << EOF
213 whatever
EOF
Or:
var=(
123 456
)