I am working on my programming language which compiles into bash 4.3+ code. I am in the final stages of my language, but I have a small issue with recursion functions. Here's the bash code which is supposed to return the fibnacci number given an index.
#!/bin/bash
function fib() {
local a=$1
declare -n ret=$2
if (( $a <= 2 )); then
ret=1
return
fi
fib $((a-1)) fib1
fib $((a-2)) fib2
ret=$((fib1+fib2))
echo "fib($((a-1))) + fib($((a-2))) = $ret"
return
}
num=5
fib $num result
echo
echo "fib($num) = $result"
The problem in this code is that the fib(5) is giving 3 which is clearly wrong. What I think the problem is, when I pass fib1 and fib2 as a way to store the return value, they get overwritten by each call which assigns them. If that was the problem, how can I make fib1 and fib2 local to their execution scope.
Please note that I do not want to use a return statement to return the values, I want to try finding a solution using declare -n namerefs.
Thank you
What I think the problem is, when I pass fib1 and fib2 as a way to store the return value, they get overwritten by each call which assigns them.
Yep, and you can see that by printing the value of fib1 between and after the recursive calls:
fib $((a-1)) fib1
echo "fib($a): fib1: $fib1"
fib $((a-2)) fib2
echo "fib($a): fib1: $fib1 fib2: $fib2"
You should see the value of fib1 change during the second call. That's to be expected, since it wasn't declared local and there only is one global copy of fib1.
If you make them local... it doesn't help much.
Assume you start by calling fib 4 result. The first iteration will make fib1 local, and call fib 3 fib1. Now the second iteration will also make fib1 local, but it will also try to assign its return value to a variable of the same name. Since the access is by name, it saves the return value to its own copy of fib1.
This can be seen with a somewhat simpler script too, this tries to return a fixed value up from the bottom of the recursion:
#!/bin/bash
foo() {
declare -n ret=$2
if (( $1 == 0 )); then
echo "foo($1) returning"
ret=end # this is the value that should bubble up
return
fi
local x=initial$1 # use $1 here to track the level the value came from
foo $(($1 - 1)) x
ret=$x
echo "foo($1) = $x"
return
}
foo 3 result
echo "main: $result"
The workaround I can think of is to have a separate global variable for the return value, and to immediately copy it to a local variable:
local fib1 fib2
fib $((a-1)) retval
fib1=$retval
fib $((a-2)) retval
fib2=$retval
Related
Say I have the following code where is_wednesday is a function that returns 0 on Wednesdays and 1 on other days.
print_wednesday() {
is_wednesday && local WEDNESDAY="Yes!" || local WEDNESDAY="No!"
echo "Is today Wednesday? $WEDNESDAY"
}
Is there a way that assigning a value to a local variable would return 1, which in this example would result in printing Is today Wednesday? No! on a Wednesday?
Yes, a (simple, syntactically correct) local variable assignment can return false. In particular, it happens if the variable has already been declared readonly elsewhere in the code, or declared with declare -r outside of a function. This Shellcheck-clean program demonstrates the issue:
#! /bin/bash -p
readonly WEDNESDAY='No!'
function test_local_return_value
{
local WEDNESDAY='Yes!'
echo "'local' returned $?"
echo "WEDNESDAY='$WEDNESDAY'"
}
test_local_return_value
The output looks like:
...: line 7: local: WEDNESDAY: readonly variable
'local' returned 1
WEDNESDAY='No!'
This is a serious limitation of local because it means that changes elsewhere in a program can break a function that appears to be completely self-contained. I have seen this happen in practice. The problem is made worse by the fact that readonly creates a global read-only variable even if it is used in a function. readonly needs to be used very carefully. It shouldn't be used in functions (use local -r instead) and it it's best to have a naming convention that ensures readonly variable names don't clash with other variable names.
The readonly issue is covered in the Bash man page (at least for Bash version 4.4). The section on local includes: "The return status is 0 unless local is used outside a function, an invalid name is supplied, or name is a readonly variable".
Can local variable assignment return false?
The built-in local will:
return 2 when called with --help
return 2 when called with invalid -flags.
return 1 if not called inside a function
return 0 ever otherwise
(or the whole Bash process will terminate, in case of like "out of memory" errors)
Note that variable assignment (I mean, without local) will return the exit status of the last process executed. The following will print No:
true && WEDNESDAY="Yes$(false)" || WEDNESDAY="No"
echo "$WEDNESDAY"
Is there a way that assigning a value to a local variable would return 1, which in this example would result in printing Is today Wednesday? No! on a Wednesday?
No.
I would recommend:
separate local from assignment
do not use && || chain, always use if.
do not use upper case variables for local variables.
and to write the function in the following way:
print_wednesday() {
local wednesday
if is_wednesday; then
wednesday="Yes"
else
wednesday="No"
fi
echo "Is today Wednesday? $wednesday!"
}
I am a green hand for shell scripting.
I wonder can we do something like this:
a=$((func arg))
to have it mean:
func arg
a=$?
I am struggling to understand what kind of arithmetic can we put on function return value?
Is function return value an expression of int type?
I see people do ! func arg. Why !func arg doesn't work? Aside from !, what other operation can be used in this way? I am especially interested in comparison operator.
How to do something like this in one liner?
if [ (fun arg) -eq some_random_int_literal]
Thanks!
There's nothing I know of that will automatically substitute $?, so you have to use it explicitly if you care about the specific value, not just whether it's zero or non-zero.
The simplest way I can see to write what you want is:
if [ $(fun arg >/dev/null; echo $?) -eq some_random_int_literal ]
You can leave out the redirection to /dev/null if you know the function doesn't produce output on stdout.
I was wondering if anybody could help.
I have the below code, it has an array of variables, arr, this is being passed to the method outputArray(), I require this to output each of the individual elements on their own line, when I run this code it only outputs the value of var1 and then finishes executing. The function does return a 0 or 1 (true or false) but this is functioning as expected anyway.
function outputArray() {
for i in "$#"; do
echo $i
# Condition omitted, this would manipulate data and return either 1 or 0
}
#Variable definition omitted
arr=($var1 $var2 $var3)
if outputArray "${arr[#]}"; then
echo "True"
fi
I hope this makes sense, I don't post here often so please let me know if not and I'll try again.
I am trying to count the number of times the method recurses during the life of the program. The code below gets the desired result, but uses global variables. Is there a way around this or a better way?
$count = 0
def AdditivePersistence(num)
return 0 if num.to_s.length == 1
numarr = num.to_s.chars.map!(&:to_i)
i = numarr.inject(&:+)
$count+=1
if i.to_s.length!=1
AdditivePersistence(i)
end
$count
end
Since you want the total number of recursive calls during the lifetime of the program, a global variable in some form is the only way you can do it. You can either use an explicit global variable, as you have done, or a global variable in disguise, such as a singleton class, or a thread-local variable. I will not illustrate those here since they are inferior to plain global variables for this use case.
You could take in an array with the first variable in the array being num and then the second being the count. then you just will do return [num, count]
Another option would be to update your method definition to accept the counter as an argument.
Using this approach, your method can just increment whatever counter value it receives and then pass the incremented value along in the recursive call.
def AdditivePersistence(num, counter)
return 0 if num.to_s.length == 1
numarr = num.to_s.chars.map!(&:to_i)
i = numarr.inject(&:+)
counter +=1
if i.to_s.length!=1
AdditivePersistence(i, counter)
end
counter
end
# usage
AdditivePersistence(12, 0)
I'm debugging with a core-file, so I have no active process in which to run anything.
I'm using gdb user-defined commands to inspect a bunch of data from the core file, and attempting to simplify the process using user-defined commands.
However, I cannot find a way to make the user-defined commands return values which could be used in other commands.
For example:
(note the comment on the "return" line)
define dump_linked_list
set $node = global_list->head
set $count = 1
while $node != 0
printf "%p -->", $node
set $node = $node->next
set $count = $count + 1
end
return $count ## GDB doesn't understand this return
end
Ideally, my dump_linked_list command would return the number of nodes found in the list, so that it could be used in another defined command:
define higher_function
set $total_nodes = dump_linked_list
printf "Total Nodes is %d\n", $total_nodes
end
Is such a thing possible in gdb commands?
I feel it must be, but I've been searching documentation and cannot find a mention of it, or any examples.
I found out gdb seems to pass by name which can be used to pass back a return value. A little more flexible that just using a single global variable.
(gdb) define foo
Type commands for definition of "foo".
End with a line saying just "end".
>set $arg0 = 1
>end
(gdb) set $retval = 0
(gdb) p $retval
$3 = 0
(gdb) foo $retval
(gdb) p $retval
$4 = 1
As far as I know GDB does not have such a functionality. You can set a variable of some name that you know and use it as a "return" value. For example always set the variable retval like this:
set $retval = <whatever value>
Then all your newly defined functions can use it as a return value from previously called functions. I know this is only workaround, but it is relatively simple and it works.