Why does my bash function return the wrong value?

2019-07-06 09:02发布

fact() 
{
    if [ $1 -eq 1 ]
    then
        return 1
    else
        y=`expr $1 - 1`
        fact $y
        b=$(($1 * $?))
        return $b
    fi
}
echo "enter"
read n
fact $n
echo "$?"

This is a program to find the factorial of a number. The output is correct up to 5. The output of 6 is giving as 208 but the correct answer is 720. What's causing this error?

5条回答
▲ chillily
2楼-- · 2019-07-06 09:35

Interestingly, the program works as expected using dash, it only fails using bash. So it looks like this is a bashism.

Just add the line

#!/bin/dash

at the top of the program and it works!

查看更多
Evening l夕情丶
3楼-- · 2019-07-06 09:38

What you are seeing is the wraparound of function return values at 256. 720 mod 256 is 208 (256 + 256 + 208 = 720).

My advice, if you must use a shell function, is to do:

#!/bin/bash
fact()
{
    if [ "$1" -eq "1" ]
    then
        echo 1
        return
    fi
    y=`expr $1 - 1`
    z=$(fact $y)
    echo $(($1 * $z))
}
echo "enter"
read n
echo "$(fact $n)"

This uses standard output for returning values, rather than the return code.

Or, even better, use the right tools for the job:

pax> echo 'define f(x) {if (x>1){return x*f(x-1)};return 1}
           f(6)' | bc
720
pax> echo 'define f(x) {if (x>1){return x*f(x-1)};return 1}
           f(500)' | BC_LINE_LENGTH=99999 bc
12201368259911100687012387854230469262535743428031928421924135883858
45373153881997605496447502203281863013616477148203584163378722078177
20048078520515932928547790757193933060377296085908627042917454788242
49127263443056701732707694610628023104526442188787894657547771498634
94367781037644274033827365397471386477878495438489595537537990423241
06127132698432774571554630997720278101456108118837370953101635632443
29870295638966289116589747695720879269288712817800702651745077684107
19624390394322536422605234945850129918571501248706961568141625359056
69342381300885624924689156412677565448188650659384795177536089400574
52389403357984763639449053130623237490664450488246650759467358620746
37925184200459369692981022263971952597190945217823331756934581508552
33282076282002340262690789834245171200620771464097945611612762914595
12372299133401695523638509428855920187274337951730145863575708283557
80158735432768888680120399882384702151467605445407663535984174430480
12893831389688163948746965881750450692636533817505547812864000000000
00000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000

I'd love to see a bash-only solution calculate the factorial of 500 :-)

查看更多
神经病院院长
4楼-- · 2019-07-06 09:41

Bash scripts and Bash functions return values are intended to be returned codes, and thus limited with the values they are capable of returning. You shouldn't depend on values grater then 127 (typically values that are grater then 127 - up to 255 - are used to indicate signals received).

The lines

fact $y
b=$(($1 * $?))

expect use the return code $? of fact, and this code can't be grater then 255.

The Bash way of returning values is printing them, and parsing or evaluating this output.

查看更多
Bombasti
5楼-- · 2019-07-06 09:49

The bourne shell cant store alot in $? (exit code). limit is 255. heres an alternative way

n=0
on=0
fact=1 

echo -n "Enter number to find factorial : "
read n

on=$n

while [ $n -ge  1 ]
do
  fact=`expr $fact \* $n`
  n=`expr $n - 1`
done

echo "Factorial for $on is $fact"
查看更多
手持菜刀,她持情操
6楼-- · 2019-07-06 09:54

Function return values can only go up to 255:

a()
{
        return 255
}

a
echo $?

b()
{
        return 256
}

b
echo $?

Produces:

$ bash x.sh
255
0

return is like exit and exit can only take values up to 255 (http://www.unix.org/whitepapers/shdiffs.html).

One alternative is to switch to an iterative suggestion, as described in another answer. Alternatively you can use echo and capture the recursive output that way:

#!/bin/bash

fact() 
{
    if [ $1 -eq 1 ]
    then
        echo 1
    else
        y=$(expr $1 - 1)
        f=$(fact $y)
        b=$(($1 * $f))
        echo $b
    fi
}
echo "enter"
read n
fact $n
查看更多
登录 后发表回答