-2

as I have already explained in my other thread While statement: Cannot get compound of multiple conditions to work I am trying to parse the input arguments in a recursive descent fashion.

Sadly, the thread, although the participants were by far more knowledgable than me and gave good advice, the problem still persists.

Also I poked around myself some further in the console with no avail.

I have following two while statements in my script, which are problematic, because obviously the endOfInput function has no influence on ending the while loop.

I would poke around and sometimes it wouldn't even enter the loop.

So the outer while-statement is the following:

while [ $current_token_index -le $ARGC ] && [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do

Also, again, here is the inner while-statement

while [[ "$current_token_index" -le "$ARGC" ]] && [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do

Also the helper methods:

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=false

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

Additionally the eat! function that gets us the next token from a copy of the positional

eat! () {
        current_token=${ARGV[$current_token_index]}
        ((current_token_index += 1))
    current_char=${current_token:0:1}

}

And above that I declare

# as set by eat!()
current_token=""
current_char=""

current_token_index=0 curent_char_index=0

My question refers to the possibility, that the endOfInput (originally: endOfInput? but I deleted the ? as I learned, in bash it has a wildcard meaning which can lead to problems. In Ruby you can choose most special characters as identifiers with no problems. Obviously there are many caveats in bash if you come from other programming languages) ... so that the truth value of endOfInput function is not evaluated correctly via multiple definitions of the function together with different testing or comparing syntax in a while-statement with its own exigences towards evaluating whether or not a condition holds or not. So there are three things in the equation that together are responsible for the formulation of the conditional constellation.

The problem with the while loop heads as posted above are that either they aren't entered or they don't stop. Depending on how I test endOfInput or how I group it.

If I replace ! endOfInput && ... just by ! false && ... for instance the while loop would be entered, in the other case not.

This is why I conjecture that there must be a problem with the function. It is not correctly evaluated. The problem may be 1) the definition of endOfInput, or 2) how it gets tested that is,

  • 2.1 with what test (things like -eq, string comparison like =, arithmetic operators like ==)

  • 2.2 what is tested. I.e.

    • 2.2.1 A string "true"/"false"
    • 2.2.2 true and false as literals
    • 2.2.3 0 for true and 1 for false
  • 3 How is this value correctly returned?

    • 3.1 by return
    • 3.2 by exit
    • 3.3 by echo
  • 4 by what testing construct is the returned value compared to something else?

    • 4.1 none, just executing the function
    • 4.2 brackets ([ or [[ command)
    • 4.3 arithmetic parentheses ((...))
    • 4.4 arithmetic expansion $((...))
    • 4.5 one or multiple of those combined

So please take a look at the definition of endOfInput and how it is used in the head of the while statement. What could be the problem, why there is an infinity loop? I mean, the other two functions like isWord? do work.

How should my function definition of endOfInput and how should the testing and concatenation of it in the while statement should look like such that it is correctly evaluated?

Edit: ilkkachu wanted me to post a "minimal, complete and verifiable example"

I hope the following is sufficient.

First I call get_installed_gems_with_versions to get all installed gems and their versions in an associative array. Now I have the global associative array installedGems.

Then I want to parse the option --gems by calling parse_gems_with_versions, which in turn calls parseGemVersions in order to parse a selection of the installed gems and their version into $chosenGemVersions

I let away the code of some tests that are relevant to the parsing part but not to the current problem if the not working while loop.

So here is the code

get_installed_gems_with_versions () {

unset installedGems declare -Ag installedGems

local last_key="" local values=""

while read -r line; do

line=${line##*/}

KEY="${line%-*}"

    VALUE="${line##*-}"

    if [ -z ${installedGems[$KEY]+x} ]; then

        echo "key $KEY doesn't yet exist."
        echo "append value $VALUE to key $KEY"

        installedGems[$KEY]="$VALUE"
        continue

    else
        echo "key already exists"
        echo "append value $VALUE to $KEY if not already exists"
        installedGems[$KEY]="${installedGems[$KEY]} $VALUE"
        echo "result: ${installedGems[$KEY]}"
    fi 

done < <(find $directory -maxdepth 1 -type d -regextype posix-extended -regex "^${directory}\/[a-zA-Z0-9]+([-_]?[a-zA-Z0-9]+)*-[0-9]{1,3}(.[0-9]{1,3}){,3}\$")

}

parseGemVersions () {

local version_list

declare -Ag chosenGemVersions

while [[ "$current_token_index" -le "$ARGC" ]] && [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do

        if versionOfGemInstalled? $gem $current_token; then

            if [ -z ${chosenGemVersions[$gem]+x} ]; then

                chosenGemVersions[$gem]=$current_token
            # continue
            else
                chosenGemVersions[$gem]="${chosenGemVersions[$gem]} $current_token"
            fi

        else
            parsing_error! "While parsing function $FUNCNAME, current version number $current_token is not installed!"
        fi

        echo "result: ${chosenGemVersions[$gem]}"

    eat!

done

}

parse_gems_with_versions () {

option --gems

--gems gem-name-1 1.1.1 1.2.1 1.2.5 gem-name-2 latest gem-name-3 ...

unset gem
unset chosenGemVersions

gem=""
declare -Ag chosenGemVersions

    while [ $current_token_index -le $ARGC ] && [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do

            if isWord? $current_token && [ ! "$current_token" = "latest" ]; then
                    if isWord? $current_token; then
                        if gemInstalled? $current_token; then
                            # We can conjecture that the word token is in fact a gems' name
                            gem=$current_token

                            local version_list

                            if isWord? $current_token && [ "$current_token" = "latest" ]; then
                                version_list=(${installedGems[$gem]})
                                chosenGemVersions[$gem]="${version_list[${#version_list} -1]}"
                            else
                                # gets list chosenGemVersions
                                parseGemVersions
                            fi
                        else
                            parsing_error! "Gem $token_name not installed!"
                        fi

                    fi
         else
            parsing_error! "While parsing function $FUNCNAME, "latest" is not a gemname!"
         fi

        eat!

    done

}

von spotz
  • 435
  • Why the two downvotes and the closing wish? What exactly is wrong with my question? I clearly describe a clear problem. The while statement in compound with its' conditions goes into an infinite loop. I can't explain why. Where did I do something wrong? – von spotz Jun 05 '21 at 10:58
  • You don't have a function called endOfInput, the code you showed only has a function called endOfInput?. Which ever it is, it wouldn't affect the conditions in those two while loops, because neither of them seems to a) call the function, nor b) use the variable EO_ARGS set by the function. – ilkkachu Jun 05 '21 at 12:29
  • 1
    As for what the downvotes are for, take a look at what a "minimal, complete and verifiable example" (or "minimal and reproducible exampe") is: https://stackoverflow.com/help/minimal-reproducible-example and https://meta.stackoverflow.com/questions/349789/how-do-i-create-a-minimal-complete-verifiable-example That is to say, show the code, the whole code, drop the unnecessary parts and the irrelevant chatter, and tell exactly what it is you're trying to achieve – ilkkachu Jun 05 '21 at 12:31
  • 1
    Part of what is needed for a complete example is the whole script, in full, and the command used to run it, and the inputs to it. That the being the stuff the reader needs to run your code. Also, you need to show the outputs you expect to get, because, well, how else would anyone other than know what it is you want to get. Here, you only show some functions, but nothing about how they're called. We can't know what your script contains. – ilkkachu Jun 05 '21 at 12:39
  • Also, if you want someone to look at what your script is doing with the arguments, you need to tell what it's supposed to do, in English. You wrote here that "If I replace ! endOfInput && ... just by ! false && ... for instance the while loop would be entered, in the other case not.", which hints at endOfinput not doing what you expect it to do, so you might want to look there. (Well, minus the fact that your code here doesn't contain a line with ! endOfInput &&...) – ilkkachu Jun 05 '21 at 12:40
  • I had the question mark removed. But however, the function is out of the equation anyway because I substituted it for a bracket test which should give correct exit codes or whatever to the while loop. And the relevant parts are the while loops as posted. – von spotz Jun 05 '21 at 12:43
  • In the last thread I was indeed more unsure what I should post at all or how I should ask. I think I made it better in this thread apart from accidentally posting the old named version of endOfInput

    And I did convey what it is what I want to do. I am parsing the arguments of a script "in the fashion of" a https://en.wikipedia.org/wiki/Recursive_descent_parser

    – von spotz Jun 05 '21 at 12:46
  • Hello ilkkachu, is that sufficient? – von spotz Jun 05 '21 at 13:43
  • ok, looking at that part you added, it's easy to tell it's not a complete program, because it just defines functions, but doesn't show the main program that would call them. So, the reader still can't tell what inputs it takes. In the least, the code is missing the eat! function. Now, that one we can look at, since you did include it separately, and we can see it sets current_token, which is a major which shellcheck.net notes that is used but not set in your code. But then eat! uses an array called ARGV, and nowhere is it shown what that is. – ilkkachu Jun 06 '21 at 20:55
  • At this point, I'm just going to stop repeating myself. – ilkkachu Jun 06 '21 at 20:56

2 Answers2

2

I believe your problem is mainly in misunderstanding boolean type in shell scripts.

You can basically replace the first helper functions with these:

#!/bin/bash

set -o nounset set -o errexit

is_word () { local pattern='^[a-zA-Z0-9_-]+$' [[ "$1" =~ $pattern ]] }

is_version_number () { local pattern='^[0-9]{1,2}(.[0-9]{,2})*$' [[ "$1" =~ $pattern ]] }

testing is_word ()

if is_word 'bla01'; then echo IS word else echo NOT word fi

testing is_version_number ()

if is_version_number '1.1'; then echo IS version number else echo NOT version number fi

You see, do not try to echo true/false or anything, the test command ([..] or [[..]]) itself returns proper boolean (actually integer), never use your own true/false constructs.

Additionally, always use ShellCheck (https://www.shellcheck.net/) to bullet-proof your scripts, or debug them.

Good habit also is using set -o nounset (set -u) and set -o errexit (set -e).


With the updated version of your question, I see you are used to C programming probably. There is no $ARGC, nor $ARGV in shell scripts. Use these instead:

  • ARGC (argument count): "$#"

  • ARGV (argument array): "$@"


For example, you could do this:

eat_args ()
{
    for arg in "$@"; do
        current_char=${arg:0:1}
        echo "$current_char"
    done
}
eat_args "$@"

which does not make much sense to me, as it prints the first letter of each given argument. Cheers and good luck.

  • It won't even enter the second statement: while [ $current_token_index -le $ARGC ] && isVersionNumber? "$current_token"; do or with while [ $current_token_index -le $ARGC ] && [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do – von spotz Jun 05 '21 at 07:07
  • so you are saying I can't / shouldn't write own testing functions? Then the only alternative is either string comparison or brackets, right? – von spotz Jun 05 '21 at 07:15
  • 1
    thanks for the link. Seems like a helpful tool ! – von spotz Jun 05 '21 at 07:17
  • How can I write correct helper functions for conditional tests then? Also, why does the while loop run infinite? while [[ "$current_token_index" -le "$ARGC" ]] && [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do an echo shows that $current_token_index is 1201 when I hit CTRL+C and $ARGC is 4 – von spotz Jun 05 '21 at 10:35
  • 1
    @vonspotz Sure you can write your own testing functions, but you have to be aware that their exit value is evaluated in the boolean test, not their output. If you don't just want to use the exit value of the last statement in the function, you can still put in an explicit return with the right value wherever it suits you. – DonHolgo Jun 05 '21 at 11:25
  • The first thing to learn about boolean types in shell scripts is that there are no boolean types in shell scripts... There's only strings and exit statuses, and the latter aren't much of a type since you can't store them in variables. – ilkkachu Jun 05 '21 at 12:32
  • @LinuxSecurityFreak - Regarding your update. I did those copies on purpose. Because it's the only way my script would work, because I couldn't shift the script arguments from inside other functions. So I am working on the copies which I called accordingly to the common terminology. Best wishes – von spotz Jun 05 '21 at 14:38
1

The solution is grouping the conditions connected through the || operator.

while [[ $current_token_index -le $ARGC ]] && { [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; }; do 
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
von spotz
  • 435