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, [ ]

So this is the final product. It’s pretty ugly in most places but it works.
Comments, criticisms, etc, please.

#!/bin/bash

main() {

echo "Please enter a number/string: "
read -r string   #4539319503436467                                                   
                                                                
array=()
temp=0

string=${string// /} #-strip away spaces and assign back to string

     for (( i=0; i <= ${#string}-1; i++ ))

       do
             if [[ ${string:$i:1} =~ [[:alpha:][:punct:]] ]] || [[ ${#string} -le 1 ]]

                 then

                   echo "Input can only contain digits, and more than 1 character. Try again: "
                   main

             fi                                                                                                                
       done

 ##################################################################################################

for (( x=${#string}-1; x >= 0; x-- ))

         do

               math=$((x % 2))  #if number in the 2nd position of index...

             if [[ $math -eq 0 ]]  #and is an even index number

                 then

                 temp=$((${string:$x:1}*2)) #double *value*, and assign it to $temp     
  
                   if [[ $temp -gt 9 ]]  #if $temp is greater than 9
  
                     then
                     array[$x]=$(( "temp - 9"))  #subtract 9 and assign to array   
  
                     else
                     array[$x]=$temp

                   fi        
             else
  
              array[$x]+=${string:$x:1}   #every other position but 2nd
           
             fi

         done

###################################################################################################
        
  for char in "${array[@]}"
 
      do          
        sum2=$((sum2 + char))
      done
  
         if [[ $((sum2 % 10)) > 0 ]]
  
            then
              echo "The sum is: $sum2, Sorry, Invalid.."
            else
              echo "The sum is: $sum2, Valid!"
         fi  

} #<-end of main()

 main

Also, is it possible to increase or decrease the verbosity of set -x? Sometimes I’d like to turn it up or down.

As far as I know there is no verbosity level. It’s “show all commands executed” or not.

You may, of course, set +x whereever you want to stop showing the commands - so you can enclose interesting sections for debugging.