Discussion:
To understand recursion, you first need to understand recursion (and Joey Hess obviously doesn't)
(too old to reply)
Rainer Weikusat
2024-02-05 15:37:48 UTC
Permalink
Debian (and derived distros) use a system called Debconf for prompting
users for configuration information during package installation. In the
advanced section, the manpage suggests that config scripts should support
moving backwards through multiple question so that users can fix mistakes
and then states

There are several ways to write the control structures of your
program so it can jump back to previous questions when
necessary. You can write goto-laden spaghetti code. Or you can
create several functions and use recursion. But perhaps the
cleanest and easiest way is to construct a state machine. Here is
a skeleton of a state machine that you can fill out and expand.

This is followed by a 40 line code example using case (Bourne shell) to
implement a form of computed goto in a loop with lots of explicit state
handling and switching code.

Considering the problem, there's a set of questions which needs to be
asked in order. If a user OKs an answer, the next question should be
asked, if he cancels it, the previous question should be asked
again. This absolutely cries for a recursive solution and there's a
really simple and general one which needs much less code than the
example in the manpage.

First, define a helper function for actually asking a question:

ask()
{
db_input high "$PREFIX/$1"
db_go
}

Given that, a function for asking a set of question which can back up to
the previous one can be written as

ask_all()
( # run in forked subshell so that variables are local

# termination condition: called w/ no argumnets,
# ergo, nothing to do, return success
test "$1" || return 0

# remove our question from argument list
q="$1"
shift

while true;
do
# ask question, return failure on failure (2 arbitrary)
ask "$q" || return 2

# invoke ask_all again to ask remaining question, return success on success
ask_all "$@" && return 0
done
)
Geoff Clare
2024-02-06 13:47:17 UTC
Permalink
Post by Rainer Weikusat
# termination condition: called w/ no argumnets,
# ergo, nothing to do, return success
test "$1" || return 0
Nit-pick: the comment doesn't match the code. Should be either:

# termination condition: called w/ no arguments,
# ergo, nothing to do, return success
test "$#" -eq 0 || return 0

or:

# termination condition: called w/ no arguments or with an
# empty first argument, ergo, nothing to do, return success
test "$1" || return 0

In the latter case I would also usually use test -n "$1" just in
case it ever gets run with a version of test that doesn't follow
the POSIX single-argument rule correctly. (I believe pre-POSIX
Bourne shells when given test "!" would produce an error complaining
of a missing argument.)
--
Geoff Clare <***@gclare.org.uk>
Geoff Clare
2024-02-06 14:28:51 UTC
Permalink
Post by Geoff Clare
# termination condition: called w/ no arguments,
# ergo, nothing to do, return success
test "$#" -eq 0 || return 0
What's that law about posts pointing out mistakes always containing
a mistake?

I failed to notice the new test has the logic the other way round
from test "$1". So it should be:

test "$#" -eq 0 && return 0

or:

test "$#" -ne 0 || return 0
--
Geoff Clare <***@gclare.org.uk>
Rainer Weikusat
2024-02-06 16:41:38 UTC
Permalink
Post by Geoff Clare
Post by Rainer Weikusat
# termination condition: called w/ no argumnets,
# ergo, nothing to do, return success
test "$1" || return 0
# termination condition: called w/ no arguments,
# ergo, nothing to do, return success
test "$#" -eq 0 || return 0
# termination condition: called w/ no arguments or with an
# empty first argument, ergo, nothing to do, return success
test "$1" || return 0
In theory, that's correct. In practice, passing empty arguments to this
functions serves no useful purpose, hence, that's something the calling
code isn't supposed to do. I've also just inserted the comments to
explain the example, they're not in the actual code.

Loading...