Bash luhn exercise

I can’t figure out why this is not working:

echo "Please enter a number/string: "  #4539319503436467
  read string
   
       set -x
       array=()
       
     for (( x=${#string}-1; x >= 0; x-- ))
  
         do
  
             echo "x is:$x"
             array+={string:$x:1} # or array+=( $string:$x:1 )
                               
         done                    
  
  declare -p array

The output I’m getting:

Please enter a number/string: 
4539319503436467    
+ array=()
+ (( x=16-1 ))
+ (( x >= 0 ))
+ echo 'x is:15'
x is:15
+ array+='{string:15:1}'
+ (( x--  ))
+ (( x >= 0 ))
+ echo 'x is:14'
x is:14
+ array+='{string:14:1}'
+ (( x--  ))
+ (( x >= 0 ))
<----------SNIP------------->
declare -p array
declare -a array=([0]="{string:15:1}{string:14:1}{string:13:1}
{string:12:1}{string:11:1}{string:10:1}{string:9:1}{string:8:1}
{string:7:1}{string:6:1}{string:5:1}{string:4:1}{string:3:1}
{string:2:1}{string:1:1}{string:0:1}")

or I get the full input string: {4539319503436467:15:1}

Did you mean array+=${string:$x:1} ?

This is a tricky bit of bash syntax. array is defined to be an array.

To append to it, you need to still use parentheses

array+=( "a new element" )

Where you’re getting hung up is with the variable expansion syntax. To extract a substring starting at offset and extending for length characters, you must use this syntax, with the dollar and the braces.

${varname:offset:length}

Put that together:

array+=( "${string:$x:1}" )

The double quotes are needed here.


Bash arrays are odd. If you start to treat the variable as a plain “scalar” variable, you intract with the element at index 0

array=(11 22 33)
echo ${array[0]}   # => 11
echo $array        # => 11
array+="hello"
echo ${array[0]}   # => 11hello
echo $array        # => 11hello
declare -p array
# => declare -a array=([0]="11hello" [1]="22" [2]="33")

This exercise calls for doubling every 2nd digit of the input from the top.
So in this case using 4539319503436467 as the input, it would be 6, 6, 4, 0, 9, 3, 3, 4

I can’t get my head around how to do this. I’m thinking something like this:

echo "Please enter a number/string: " #input-4539319503436467
read string

for (( x=${#string}; x >= 0; x-- ))
 
         do
  
             if [[ x is in the 2nd position ]] 
  
                then
                      $temp=$x
                      $temp*2
  
             fi

Any suggestions?

Typically you’d check if a number is even or odd by using the var % 2 operator. “2nd position” means its index is one or the other of those.

Ah, I see. That’s clever.
I believe the logic of the structure works, but the problem I’m running into now is there are 3 different assignments to the array, and each one doesn’t append/assign individually to each of the 16 elements of the index. The assignments either overwrite an existing element, or there are multiple assignments to the same element. I’m sure it has something to do with the syntax.

#!/bin/bash
   
   echo "Please enter a number/string: "  #4539319503436467
   read -r string
   
       set -x
       array=()
       temp=0
  
   for (( x=${#string}-1; x >= 0; x-- ))
  
         do
               math=$((x % 2)) #if number is every 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=$(( temp - 9 )) #subtract 9 and assign to array                                             
  
                 else  
                     array=$( "$temp" )
                 fi
            
             else
                array+=( "${string:$x:1}" ) # odd index numbers     
                declare -p array        
                
             fi
  
         done                    
  
  declare -p array

I’m trying to get each assignment to the array in it’s own index. I don’t think the direction matters.

Any suggestions?

  • var=value sets a variable. It replaces any prior values.
  • var+=( value ) appends a value to an array.
  • var[index]=value sets an array element by index.

You might want to read up on basic Bash syntax. It makes writing Bash a lot easier than guessing at syntax.

I solved it using a different method.

I added $x from the for loop, to each array assignment. That way, it did the assignment for each index it encountered along the way. I couldn’t get it to work the other way. I was getting multiple values in the same element, values overwritten, etc.

New way:

array[$x]=$(( "temp - 9" ))
array[$x]=$temp
array[$x]+=${string:$x:1}

Old way:

array+=$(( temp - 9 ))
array+=("$temp")
array+=( "${string:$x:1}" )

So, my question is, was I on the right track with the old method? Is there a way to do it that I’m missing? Or was I way off?

You could try describing what you’re trying to do in English/pseudocode and we can show how that’s done.

It’s really hard when someone says, “Here is code that doesn’t accomplish X. Is this on the right track to accomplish X?” We don’t know what X and we can’t derive X from the code which doesn’t do X. That makes it hard to tell if the code (which doesn’t do X) is close to doing X (whatever X might be).

It’s the same code as the previous post. Each time there is an assignment to the array, I started using $x from the for loop:

array[$x]=$(( "temp - 9" ))
array[$x]=$temp
array[$x]+=${string:$x:1}

Before I was just trying:

array+=$(( temp - 9 ))
array+=("$temp")
array+=( "${string:$x:1}" )

Which you can see in the code below.
This was not working, so just wondering if I was missing something.

#!/bin/bash
   
   echo "Please enter a number/string: "  #4539319503436467
   read -r string
   
       set -x
       array=()
       temp=0
  
   for (( x=${#string}-1; x >= 0; x-- ))
  
         do
               math=$((x % 2)) #if number is every 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=$(( temp - 9 )) #subtract 9 and assign to array                                             
  
                 else  
                     array=$( "$temp" )
                 fi
            
             else
                array+=( "${string:$x:1}" ) # odd index numbers     
                declare -p array        
                
             fi
  
         done                    
  
  declare -p array

One of these assigns a value to the same position/index/key $x in the array three times while the other appends a new element three times.

Saying that this is the same code from before, reiterating that this code doesn’t work and showing the broader file all fail to explain what you are hoping to accomplish with this code. Intent should be expressed in regular English, not by providing code.

This is the prompt for the same exercise.

I’m trying to check if the user input contains anything that is not a digit, or is 1 character or less in length, and produce an error if any of these conditions are true.

echo "Please enter a number/string: "
read -r string

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

do

  if [[ ${string:$i:1} =~ [^[:digit:]] || ${string:$i:1} | wc -m -le 1 ]]
     then
       echo "Sorry contains a non-digit, or input is 1 character or less. Try again."
     else...
  fi

done

In the if statement, if I take out the 2nd condition, the regex seems to work on it’s own. If I add the ${string:$i:1} | wc -m -le 1 back, it stops working.

In that 2nd condition, I’m trying to pipe the current character of string into wc, then test if it’s less than or equal to 1.

Any hints are appreciated.

You need to use valid bash syntax ;) You can’t just put things on a line and hope bash would figure out what you want it to mean.

How exactly are you hoping bash to interpret that line?

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

The only part that I’m not sure about is wc -m -le 1

The rest of it seems valid to me: Just a typical if statement with an or put between two conditions. I’m not sure which part is invalid.

I certainly could have messed up the regex part, I know essentially nothing about regex.

If you’re not sure it works, then you should double check where you came up with it ;) Does the code otherwise work with that part removed? Then that would suggest that part isn’t working. This is how you narrow down the issue to isolate the problem.

wc is not a valid test inside [[.

I’ve tried to do the same test outside of [[, and isolating it. It still doesn’t work.

temp2=0

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

do

  ${string:$i:1} | $temp2=$wc -m
       echo "$temp2"
        
  if [[ $temp2 -le 1 ]]
        
       then

       echo "Sorry, input is 1 character or less. Try again."
     
  fi

done

I get set -x output like this:

 Please enter a number/string: 
45
+ (( i=0 ))
+ (( i <= 2 ))
+ 4
./luhn.sh: line 29: 4: command not found
+ 0= -m
./luhn.sh: line 29: 0=: command not found
+ echo 0
0
+ [[ 0 -le 1 ]]

Looks like it’s not assigning the value of the input to temp2, which is just 0.

This isn’t valid bash syntax. What are you hoping this would accomplish? Does this work in your terminal?

${string:$i:1} | $temp2=$wc -m

I get a similar error when trying it in a terminal.

I’m trying to pipe the single digit of string into wc -m, and assign that value to temp2.

That’s neither how you’d pipe a string to a command nor how you’d assign the output of a command to a variable. You might want to read up on basic shell syntax rather than making up syntax, hoping it will work and asking why it doesn’t work :slight_smile: The Wooledge Guide is pretty solid. If you work your way through that, the Luhn exercise should be pretty easy.