-1

I try to parse arguments by a recursive descent schema.

The while statements calls an eat! function after every execution to get the next $current_token to process.

The problem now is that this way it goes into an infinite loop.

To be exact, there are two while loops in the function.

Either one of them or both are not working because the compound is syntactically (or logically?) wrong.

Could you please check what might be wrong with the following code on the many levels possible?

1)

while $(isWord? $current_token) || ! $(isVersionNumber? $current_token) && ! $(endOfInput?); do
while isVersionNumber? $current_token && ! endOfInput?; do

Also what is correct? To put the checking-functions in a command substitution or not?

Could it be that short-circuiting also prevents the endOfInput?-test ?

Because that should absolutely stop the loop.

On request of ilkkachu

The test functions' code:

isWord? () {
    local pattern="^[a-zA-Z0-9_-]+$"
    if [[ $1 =~ $pattern ]]; then
        echo true
    else
        echo false
    fi
}

isVersionNumber? () { local pattern="^[0-9]{1,2}(.[0-9]{,2})*$" if [[ $1 =~ $pattern ]]; then echo true else echo false fi }

EO_ARGS=1

function endOfInput? { if [ $current_token_index -ge $ARGC ]; then EO_ARGS=0 fi echo $EO_ARGS }

I assume it outputs another command, since you're using it in a command substitution?

No it was just a try because I don't know whether or not I can just call the functions in the conditional without command substitution.

For completeness' sake, I also add the eat! function.

eat! () {

if [[ ! $(($current_char_index + 1)) -gt $ARGC ]]; then current_token=${ARGV[$current_token_index]} ((current_token_index += 1))

current_char=${current_token:0:1} }

And may the conditions maybe be formulated better with test / [ ([[) ?

Could the exclamation mark be the point of failure?

The following seems to be syntactically wrong:

while [ endOfInput -eq 1 ] && isWord? "$current_token" || ! isVersionNumber? "$current_token"; do

From the bracket [ I get the error message

[: endOfInput: integral expression expected (translation)
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
von spotz
  • 435
  • 1
    You're declaring commands/functions with ? and ! characters? That's brave – Chris Davies Jun 04 '21 at 13:17
  • 1
    Here's your truth table. Does it match your expectation? printf "X1\tX2\tX3\tResult\n"; for x1 in false true; do for x2 in false true; do for x3 in false true; do printf "%s\t%s\t%s\t" $x1 $x2 $x3; if $x1 || ! $x2 && ! $x3; then echo true; else echo false; fi; done; done; done – Chris Davies Jun 04 '21 at 13:20
  • In ruby that's not a problem. Okay, so question and exclamation marks are forbidden by function naming conventions? – von spotz Jun 04 '21 at 13:20
  • They're not fobidden, no, but I wouldn't say they were expected (originally I even thought they were bad characters and had to go to check) – Chris Davies Jun 04 '21 at 13:21
  • 1
    while command && ! command; do : ...; done (i.e. your #2) is correct syntax – Chris Davies Jun 04 '21 at 13:22
  • 1
    sigh. What's isWord?? You're not showing that part of the code. It's a command, right? Which does, what exactly? I assume it outputs another command, since you're using it in a command substitution? If you were to [edit] the question to remove all the unnecessary chatter, and focus on presenting what you're trying to do, it might be easier to give answers – ilkkachu Jun 04 '21 at 13:23
  • Thanks for the last response. Your truth table script is probably helpful for more advanced (bash?) programmers than me, but I right now cannot "get" it through this, although I see the effort. So thanks for that too. – von spotz Jun 04 '21 at 13:24
  • 1
    ok, right. There's the three test functions. What about this part: "The while statements calls an eat! function"? Is that eat! function relevant? If not, you can leave this sort of statements out, they only work to distract. If the problem is with the loop conditions, stick to them. If the function is relevant, then you probably need to show that one too. "To be exact, there are two while loops in the function." -- also these? Which two while loops? – ilkkachu Jun 04 '21 at 13:35
  • @ilkkachu No it's not relevant so I skipped it. But for completeness sake I will also add it. Thanks. – von spotz Jun 04 '21 at 13:35
  • 1
    About the loop condition, well, you still didn't tell what you expect it should do, based on the individual test functions. If you were to tell that, the reader could check if e.g. the logic is correctly translated to the computer language. (People get caught on && and || rather often.) Now we need to guess if that's the problem, or if you know that part already. And yes, I asked about command substitution, since you used it there, so I expected you'd know what it does. And what the difference is between $(foo) and foo. – ilkkachu Jun 04 '21 at 13:39
  • 1
    See the update of my reply on ? and ! in function names. – choroba Jun 04 '21 at 13:43
  • @ilkkachu No I don't know too well yet the difference between $(foo) and foo. Would be nice if you could explain but that's out of scope I guess. My problem is that I don't know how to 1) do the truth value checks while 2) connecting them by && and || correctly 3) and if the way I would do would be the way that while expects. Also, as I said, Then I don't know if other laws like short circuiting might prevent the testing of endOfInput? for example. – von spotz Jun 04 '21 at 13:44
  • @choroba Yes I did. :) – von spotz Jun 04 '21 at 13:45
  • @roaima You say that while command && ! command; do : ...; done is ok, but when I have the && ! endOfInput? part added, the loop won't be entered, although the return value is false at that time. Edit: it also prevents the first while loop from being entered. while isWord? $current_token || ! isVersionNumber? $current_token && ! $(endOfInput?); do Could it have to do with the && operator? – von spotz Jun 04 '21 at 13:52
  • 1
    @vonspotz, you still haven't told what the condition you want to test for is. In English. Nobody can tell you how to write it correctly in Bash, if they don't know what should be written. (And no, I'm not going to guess based on the names of the functions. I might as well guess wrong, and that wouldn't help anyone.) – ilkkachu Jun 04 '21 at 14:06
  • 1
    Simplifiy while isWord? $current_token || ! isVersionNumber? $current_token && ! $(endOfInput?) to while X1 || ! X2 && ! X3. Now run the truth table code I gave you earlier and check if the three inputs give you the expected output – Chris Davies Jun 04 '21 at 14:12
  • Okay, so if I insert false at the place of X3 the loop is entered. So this seems to be a logical problem rather. I will investigate. – von spotz Jun 04 '21 at 14:23
  • On the other hand, as I said in the other answer, the truth value of endOfInput? is false so I don't understand the difference why it suddenly enters the loop when I just put false in place of the function call. edit: renaming it such that it lacks the ? at the end also does nothing. – von spotz Jun 04 '21 at 14:28
  • If I group the first while while isWord? "$current_token" || ( ! isVersionNumber? "$current_token" && ! endOfInput); do then the while loop is entered. Just don't know what to do with the second while isVersionNumber? "$current_token" && ! endOfInput; do – von spotz Jun 04 '21 at 14:51
  • Can the exclamation mark be the problem? – von spotz Jun 04 '21 at 15:31
  • Check out the grouping precedence (priority) for && and ||. If you're coming from a programming language such as Ruby it may not be what you expect – Chris Davies Jun 04 '21 at 15:52
  • Thanks, but the precedence is ok. I rather think it's the connection of conditions in the while loop. It's like the && is buggy. – von spotz Jun 04 '21 at 16:56

2 Answers2

4

If a function return 0 for true and anything else for false, you can use it directly:

my-true () {
    return 0
}

while my-true ; do echo Infinite loop... done

If it outputs nothing when false but outputs something when true, use command substitution

my-true () {
    echo OK
}
while [ "$(my-true)" ] ; do
    echo Infinite loop...
done

To chain the functions in the former case, you can use the && and || operators:

while func1 && func2 ; do

In the latter case, you can use concatenation instead of ||:

while [ "$(func1 ; func2)" ] && [ "$(func3)" ]

Note that in the latter case, you can't modify global variables in the function, as it's called in a subshell.

BTW, using ? in a function name is not forbidden, but it may cause problems, as it's a wildcard:

abc? () {
    return $1
}
abc? 0   # Everything OK.
touch abcX
abc? 0   # line 9: ./abcX: Permission denied
abc\? 0  # This works, but where's the charm?

! is the default history expansion character, but that's mostly relevant to interactive shells only.

choroba
  • 47,233
  • If it outputs nothing when false but outputs something when true, use command substitution Thanks didn't know that puzzle piece either yet. – von spotz Jun 04 '21 at 13:33
  • Thanks for the additional information ! In the future I will better not use it anymore. – von spotz Jun 04 '21 at 13:47
2

Could you please check what might be wrong with the following code on the many levels possible?

Ok, fine.

Also what is correct? To put the checking-functions in a command substitution or not?

A command substitution takes the what ever the command inside prints, and puts that on the command line. (After splitting and globbing, if the expansion was unquoted.) So if you have

foo() {
    echo true
}
if $(foo); then
    echo hello
fi

then the command substitution would produce true and that would run in the condition part of the if-statement, and the exit status from it would be used to determine if the main branch of the if runs. It would, in this case.

But that's silly. You could just return the exit status from directly, without going through a command substitution (which in Bash involves a fork):

foo() {
    return 0
}
if foo; then
    echo hello
fi

Or you could just end the function with true, since the exit status of the last command is used as the exit status of the function.

So you could write:

isWord? () {
    local pattern="^[a-zA-Z0-9_-]+$"
    if [[ $1 =~ $pattern ]]; then
        return 0
    else
        return 1
    fi
}
while isWord? "$current_token"

or even:

isWord? () {
    local pattern="^[a-zA-Z0-9_-]+$"
    [[ $1 =~ $pattern ]]
}
while isWord? "$current_token"

Note that you need to quote the variable, for the all the usual reasons (i.e. word splitting and globbing).

while $(isWord? $current_token) || ! $(isVersionNumber? $current_token) && ! $(endOfInput?); do

Here, note that the a || b && c construct groups as (a || b) && c, i.e. it executes left to right. Unlike in real programming languages where an and operator has higher precedence than or (meaning it would be a || (b && c) instead). So, check which one you need.

Also, while with default settings on Bash, a name like isWord? is perhaps not a problem too often, it is still a glob. If you have a file or files matching it in the current directory, it will be expanded to those filenames. And if you switch to using failglob or nullglob, they'll affect that too. E.g. with failglob:

$ foo? () { echo hi; }
$ foo?
bash: no match: foo?
$ "foo?"
hi

You'd need to quote it to make not behave as a glob. Note that in Zsh, an unmatching glob fails by default.

The bang as the last character of a function name shouldn't matter. It could hit history expansion if it was at the start or the middle of the name, and the shell had history expansion on (interactive shells do by default, scripts don't).

Could it be that short-circuiting also prevents the endOfInput?-test ?

If you have something like while bar && ! endOfInput?; do, then yes, if bar on the left-hand side returns a falsy status, the result of the and is already known, and the right-hand side is skipped. Similarly for || and a truthy returh. That's what && and || do.

But you didn't give any context to this question, i.e. why it would matter to you if the function isn't called, so it's hard to think of better ways to do it.

ilkkachu
  • 138,973
  • But you didn't give any context to this question, i.e. why it would matter to you if the function isn't called, so it's hard to think of better ways to do it. What do you mean by that? I will gladly answer. – von spotz Jun 04 '21 at 14:10
  • Also thanks for your answer. I would also expect && to be of higher precedence than || normally, but I don't think that it should pose an interference here. I just checked the truth values of isWord?, isVersionNumber? and endOfInput? before while isWord? $current_token || ! isVersionNumber? $current_token && ! endOfInput?; do again and they should be such that the loop is being entered but it ain't. – von spotz Jun 04 '21 at 14:14
  • e.g. isWord? := true, ìsVersionNumber? := false, endOfInput? := false – von spotz Jun 04 '21 at 14:14
  • @vonspotz, what I meant about short-circuiting, is that short-circuiting never affects the truth value of the test, it only happens when the left-hand side already determines the result. So, the only thing it does, is that side effects of any commands on the right-hand side don't happen. That is, false && echo hi prints nothing: the falsy status from the left makes the && falsy, regardless of what the right side does, so the echo is skipped. If you rely on both/all commands running, always, you can't use &&. – ilkkachu Jun 05 '21 at 12:10
  • if you have true || ! false && ! false, the condition gets met. (it runs the first true and the last false.) Try with e.g. if true || ! false && ! false; then echo ok; fi. The conclusion here must be that your function doesn't return what you think it does. But again, since you don't tell what it is you're actually trying to do... – ilkkachu Jun 05 '21 at 12:12
  • @vonspotz, oh, and now your endOfInput? is different from what it was the first time? How nice. You do realize that the actual code might affect what happens, and that if you show the wrong code, any answers you're going to get will also be wrong? In other words, you're not helping anyone help you. – ilkkachu Jun 05 '21 at 12:25