bash - Can local variable assignment return false? - bash

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!"
}

Related

can't get apparently simple bash function to work [duplicate]

This question already has answers here:
How do I set a variable to the output of a command in Bash?
(15 answers)
Closed 1 year ago.
#!/bin/bash
START_SLEEP_TIME=2
LOOP_SLEEP_TIME=2
OK="false"
#need to wait for another proc first
sleep $START_SLEEP_TIME
RET=
check () {
echo "---1--"
echo "---2--"
return 1
}
# isn't even called but just making sure it's not a syntax issue
finalize () {
OK="true"
}
for i in {0..100}
do
echo "check"
VAL=check
sleep $LOOP_SLEEP_TIME
done
echo "Checks terminated. $OK"
The script is supposed to do more stuff, but I am trying to strip it down to the essential to see why it is not working.
The above for me just always prints
check
check
check
after sleeping but it never prints --1-- or --2--.
The function is supposed to be doing more stuff but if I can't even get this to work there's no point.
VAL=check assigns the value check to VAL.
If you want to call the function check and assign the output from the function to VAL, make it:
VAL=$(check)
If you instead want the function's exit value, make it:
check
VAL=$?
Just call your check function, i.e. write is as a command. The returned value is then in $?.

Pass an array to a function and output each line

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.

Bash 4.3+ - Recursive fibonacci

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

How to determine if code is executing as a script or function?

Can you determine at runtime if the executed code is running as a function or a script? If yes, what is the recommended method?
There is another way. nargin(...) gives an error if it is called on a script. The following short function should therefore do what you are asking for:
function result = isFunction(functionHandle)
%
% functionHandle: Can be a handle or string.
% result: Returns true or false.
% Try nargin() to determine if handle is a script:
try
nargin(functionHandle);
result = true;
catch exception
% If exception is as below, it is a script.
if (strcmp(exception.identifier, 'MATLAB:nargin:isScript'))
result = false;
else
% Else re-throw error:
throw(exception);
end
end
It might not be the most pretty way, but it works.
Regards
+1 for a very interesting question.
I can think of a way of determining that. Parse the executed m-file itself and check the first word in the first non-trivial non-comment line. If it's the function keyword, it's a function file. If it's not, it's a script.
Here's a neat one-liner:
strcmp(textread([mfilename '.m'], '%s', 1, 'commentstyle', 'matlab'), 'function')
The resulting value should be 1 if it's a function file, and 0 if it's a script.
Keep in mind that this code needs to be run from the m-file in question, and not from a separate function file, of course. If you want to make a generic function out of that (i.e one that tests any m-file), just pass the desired file name string to textread, like so:
function y = isfunction(x)
y = strcmp(textread([x '.m'], '%s', 1, 'commentstyle', 'matlab'), 'function')
To make this function more robust, you can also add error-handling code that verifies that the m-file actually exists before attempting to textread it.

Return a value via a gdb user-defined command

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.

Resources