Sometimes I need to run some math operations. I know I can use bc or echo $(( 6/2 )). I have created my own function for bc to read input. But sometimes it takes a long time to type: _bc "6/2". So I have this question:

Is there way to teach zsh/bash how to run math operation for numbers in command line? One example is more than thousands words.

$ 6/2
$ 3.0

It means that zsh/bash must recognize numbers and call i.e. bc.

  • 4
    This reminds me of this question (which isn’t a duplicate). Dec 6 '18 at 10:04
  • 1
    While I've never seen a script called, for example, 2+2, it could conceivably exist, in which case you'd never be able to run it.
    – Jeff Schaller
    Dec 6 '18 at 13:47
  • @JeffSchaller Or a bit more plausibly maybe 2/3. You could still run it by typing something like \2+2, if 2+2 was parsed as an alias for echo $((2+2)). Dec 6 '18 at 18:17
  • 1
    Wouldn't c 1+2+3 be a good alternative? (Calculate and then you can put anything you want, no spaces needed, same commands as bc if you want)
    – Fabby
    Dec 6 '18 at 18:56
  • in powershell 6/2 and simply works
    – phuclv
    Dec 7 '18 at 1:22

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:

 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.

 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 doesn't allow neither a + alias nor a # character.

The value will be printed as:

 $ + (56 * 23 + 26) / 17

Only a + is required, String is quoted (no globs), shell variables accepted:

 $ a=23
 $ + (56 * 23 + $a) / 17

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

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'

$ + '4 * a(1) * 2'

Which is only two characters shorter that your _bc "6/2", but a + seems more intuitive to me.

  • @Issac Thank you for your answer. Yes, it's shorter - but it's about name of function which you choose. In my own name convention has every function underscore at the beginning of name.
    – waldauf
    Dec 7 '18 at 9:40
  • @waldauf The name of the function could perfectly be named _calc or anything else.
    – ImHere
    Dec 7 '18 at 10:28
  • @Isaac Solution with keyboard shortcut looks great! I'll try to do it for bindkey in ZSH. :]
    – waldauf
    Dec 7 '18 at 11:48
  • @waldauf It seems a bit strange to both pick a convention that has an underscore at the beginning of all names (may require the shift key!) and complain about _bc taking a long time to type :-) Dec 7 '18 at 18:20
  • @ShreevatsaR - I'm sorry if you understood my question like that. I was just curious if I can type into command line some math example and ZSH/Bash recognizes math and calculate it. That's all. Really. :] And you're right - undrescore is not good name convention. I'll change it. ;]
    – waldauf
    Dec 9 '18 at 11:26

I use a variant of bash's magic alias hack:

asis() { bc <<< "$(history 1 | perl -pe 's/^ *[0-9]+ +[^ ]+ //')"; }
alias c='asis #'


$ c 1+1
$ c -10 + 20 / 5
$ c (-10 + 20) / 5
$ c 2^8 / 13
$ c scale=5; 2^8 / 13

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

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.

  • why are you saying that you can't use shell variables? you can! alias c='calc #'; calc(){ eval "$(history -a /dev/stdout | sed 's/c /expr /;s/[()*/+-]/ \\& /g')"; }; then x=33; c ($x/3)*10. This has a great potential, thanks for the trick!
    – mosvy
    Dec 7 '18 at 0:33
  • @mosvy Well, can't as written in my answer. But certainly eval raises this hack to all-new heights!
    – bishop
    Dec 7 '18 at 0:45
  • 1
    +1 Thanks for your idea, I implemented an alternative in my answer. Note that the value of HISTTIMEFORMAT needs to be cleared to avoid the time tag printed by history. Perl seems like too much for this little edition use.
    – ImHere
    Dec 7 '18 at 10:52

In zsh, you could do something like:

autoload zcalc
accept-line() {
  if [[ $BUFFER =~ '^[ (]*[+-]? *(0[xX]|.)?[[:digit:]]+[^[:alnum:]]' ]]; then
    zcalc -e $BUFFER
    print -rs -- $BUFFER
  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


  123 456

Inspired by Stéphane's zsh answer, and longer than Isaac's bash answer in total but shorter in operation:

trap '[[ $_ =~ [[:digit:]] ]] && bc -l <<< "$_"' ERR

This also has the side effect of showing a "No such file or directory" error each time:

$ foozle
-bash: foozle: command not found
$ 1+2+3
-bash: 1+2+3: command not found
$ 6/3
-bash: 6/3: No such file or directory

The regex could be tightened, depending on the operations you expect to perform.

This (ab)uses the bash behavior of calling the ERR trap when a given command does not exist. If the last command (in $_) contains a digit, then it executes bc on that "command".

Thanks to a hint from Stéphane, here's a slightly cleaner way to achieve the result (requires bash 4.0 or later, which introduced the functionality):

if ! declare -F command_not_found_handle > /dev/null
  command_not_found_handle() { 
    if [[ "$@" =~ [[:digit:]] ]]; then 
      bc <<< "$@"; 
      printf 'bash: %s: command not found\n' "$1" >&2
      return 127
  echo Unable to set up the handler function, sorry

The function is called any time a command isn't found. If that command contains a digit, we throw it through bc; otherwise, we emit a message similar to bash's stock message and return a 127 exit code.

  • Caveat - the ERR trap is not inherited to subshells, so (1+2) would need to be '(1+2)'
    – Jeff Schaller
    Dec 6 '18 at 18:55
  • 2
    bash and zsh have a command-not-found-handler hook (mispelled handle in bash) that you could also use here. Dec 6 '18 at 19:27
  • Thanks, Stéphane! I've incorporated the idea.
    – Jeff Schaller
    Dec 6 '18 at 19:49
  • 2
    command_not_found_handle doesn't work on something like 123/456, though. Bash doesn't seem to bother calling the hook on commands that look like pathnames. With the ERR trap, I think you could also use $BASH_COMMAND to get the full command line, and not just the last word. That would make expressions with spaces work.
    – ilkkachu
    Dec 6 '18 at 20:23
  • 1
    A workaround using readline is possible and seems simpler/easier.
    – ImHere
    Dec 7 '18 at 10:49

The following command lines are rather simple to type,

<<< 5+4 bc
<<< 6/3 bc
<<< 7*2 bc

and slightly more complicated with parentheses (must be quoted or escaped),

<<< "(5+4)*2/3" bc
<<< \(5+4\)*2/3 bc

Another imperfect way of doing that in Bash would be to use the DEBUG trap, which runs on every command. With extdebug set, the trap handler can prevent the main command from running, so you don't get the "command not found" errors.

$ cat bash_calc.sh
shopt -s extdebug
debug_calc() {
    local re='^[ (]*-?[0-9]'
    if [[ $BASH_COMMAND =~ $re ]]; then
        echo "$BASH_COMMAND" | bc -l
        return 1
trap debug_calc DEBUG
$ . ./bash_calc.sh
$ 123 * 456
$ 123/456

The trap gets the full command line before expanding variables or filename patterns, so an unquoted * works. (But using shell variables in the calculation doesn't work.)

However, unquoted parenthesis still cause a syntax error, so this is not perfect either.

(I nicked the regex above from Stéphane's answer.)

  • Running with extdebug active will make the shell quite slower, but it is a clever idea, thanks.
    – ImHere
    Dec 7 '18 at 10:55
  • @Isaac, hmm, that's not good. I'll admit I'd never tested it for speed, though I wonder if it matters here if this is for interactive use. Anyway, as mentioned, this is just another imperfect idea. I originally wasn't going to post it at all, but there are other somewhat hackish Bash solutions here, too, so I think it can stay.
    – ilkkachu
    Dec 7 '18 at 16:18

What about expr?

$ expr 6 / 2
  • This is the answer I was looking for. Aug 27 '19 at 19:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.