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
)