Bash luhn exercise

Ok, I’m back.

Does this check for spaces? If so, how can I avoid it?

[^[:digit:]] ]]

I’m trying to check for non-digits, but I want to skip checking for spaces.
My understanding is that the space between the ]] ]] is a check for space, but when I remove that space, the program throws errors, expecting a binary operator.

Here is the complete line:

if [[ ${string:$i:1} =~ [^[:digit:]] ]] || [[ ${#string} -le 1 ]]

This works, but I want it to let spaces pass.

Any suggestions?

You might want to double check and count the brackets. Which brackets are part of what expression?

I’ve tried removing the last two brackets from [^[:digit:]]]]:

if [[ ${string:$i:1} =~ [^[:digit:]] ]] || [[ ${#string} -le 1 ]]

I think all of the brackets are accounted for now.

When watching the output with set -x, this seems to allow spaces to pass.

Any suggestion?

Why part of that expression would catch/check for spaces?

Conversely, do you understand what that code does? Can you break it down into it’s parts and explain each bit?

I though removing the last ]] from the [^[:digit:]] ]] would take the check for space out of it, but spaces are being caught again.

if [[ ${string:$i:1} =~ [^[:digit:]] ]] - This is trying to say “if the current char of string is NOT a digit”

|| or

[[ ${#string} -le 1 ]] - The full length/index of string is less than, or equal to 1.

But somehow spaces are still being caught.

Can you break if [[ ${string:$i:1} =~ [^[:digit:]] ]] into its parts? Is [^[:digit:]] ]] part of one logical component?

I do think of it as part of one logical component; the first condition of the if statement, before the ||.

I don’t fully understand regex, so to the best of my knowledge, [^[:digit:]] ]] says, NOT a digit, meaning not 0-9. That last ]] is for closing the if.

But a space is a non-digit, so maybe I should be using ^[0-9]? From what I’ve read, there are subtle differences between [^[:digit:]] ]] and ^[0-9], and I don’t fully understand them.

I’ve tried removing some sets of brackets, but it didn’t help.

Which brackets would you remove and how would that change the meaning of the expression? Why/how would that change the behavior? Deleting things randomly isn’t a good way to accomplish anything.

What is the meaning of ${string:$i:1}? What exactly are you checking/testing?

Can you explain in English what you’re trying to accomplish and the steps to get there?

if [[ ${string:$i:1} =~ [^[:digit:]] ]]

  1. if runs a command and optionally branches based on the command’s exit status. The command evaluated here is [[ ${string:$i:1} =~ [^[:digit:]] ]].
  2. [[ ${string:$i:1} =~ [^[:digit:]] ]] evaluates an expression. The expression it tests is ${string:$i:1} =~ [^[:digit:]]. Note, the closing ]] matches the opening [[ and is not part of the “test”.
  3. The [[ expression ${string:$i:1} =~ [^[:digit:]] does a regex match (=~) using the value ${string:$i:1} and the regex [^[:digit:]].
  4. The regex [^[:digit:]] matches any chars that are not digits.

There is no [^[:digit:]] ]] atom in this code in any logical way.

If any of these parts were not already apparent to you, you should slow down and understand the code before trying to modify it. Trying to modify the behavior of code without first understanding the basic structure of code isn’t programming; it’s brute force randomness.

I fully understand 1-4, but I don’t understand:

There is no [^[:digit:]] ]] atom in this code in any logical way.

By atom, I think you mean a single element? That comes from the user input which contains a space, just as a test:

echo "Please enter a number/string: "
read -r string        #45393195 03436467 - test string/input                                          
                                                                           
set -x

     for (( i=0; i <= ${#string}-1; i++ )) 
    
       do                                
              if [[ ${string:$i:1} =~ [^[:digit:]] ]] || [[ ${#string} -le 1 ]]          
                  
              then        
                   echo "Input can only contain digits, and more than 1 character. Try again: "
                   
                   main            
              fi                 
       done

Am I wrong to say that a space is also a non-digit, that would be caught by [^[:digit:]] ? I’m not sure how I could alter it to avoid spaces. That’s why I mentioned trying ^[0-9], as I see it being more explicit (only numbers, no special chars, or spaces), although I would want to catch special chars.

I’m saying the regex is [^[:digit:]] and the trailing ]] in [^[:digit:]] ]] is not part of the regex, The regex has exactly two [ and two ]. You can’t drop any of them. There is no space in the regex. As such, the following doesn’t make much sense:

Why would you test a regex against individual characters? The whole point of regexes is for string matching?

A space is a non-digit. [^[:digit]] would match a string.

» string='45393195 03436467'
» for (( i=0; i <= ${#string}-1; i++ )); do 
    [[ ${string:$i:1} =~ [^[:digit:]] ]] && echo "String contains a non-digit, [${string:$i:1}]"
done

===>
String contains a non-digit, [ ]