Pangram excersise

I am attempting the Pangram excercise. There is a read command that puts user input into the array sentence.

But what is wrong with this while loop? I get 26 lines of : command not found

alphabet=(a b c d e f g h i j k l m n o p q r s t u v w x y z)

for char in "${!alphabet[@]}"
 16 
 17 do
 18 
 19    while "${sentence[char]}" -ne "${alphabet[i]}"
 20      do
 21         ((i++))
 22      done
 23 
 24  ((pangram_counter++))
 25 
 26 done

Any help is appreciated!

  1. read reads from STDIN which will likely cause the tests to timeout (since nothing is writing to STDIN) even if everything else works.
  2. while takes a command. Whatever "${sentence[char]}" expands to it likely not a valid command. a and b and c etc are all not commands, as far an I know.

A while and if testing commands, the construction

"${sentence[char]}" -ne "${alphabet[i]}"

isn’t a valid command, both arguments between quotes will be replaced by their values (if any) and executed as if you had written the values ​​directly into a line of a script or on the command line. Your message error come from this construction.

Anyway, what you probably want in this case is:

while [[ "${sentence[char]}" -ne "${alphabet[i]} ]]

This will lead you to other problems in this script, but it’s good to solve one problem at a time.

For instance, you can debug your script using set -x.

That means you have a file with DOS-style \r\n line endings.

Bash splits the script file on newlines. The carriage return is not a special character. Bash thinks it is part of the command to run. Since it can’t find a command that literally ends with the \r character, bash emits a “command not found” error.

The format of that error message is

bash: %s: command not found

Due to the carriage return, the cursor goes back to the start of the line and the first part of the error message is overwritten.

@IsaacG I don’t understand what you said about read.

Are you saying it has to have a constant stream of input, instead of just passing the value to it’s variable? Like:

read -a sentence

Also,

How would I increment alphabetic characters;
if i in "${alphabet[i]} represents a-z, would I use ((i++)), or ((i+1)) to cycle through the alphabet?

Thanks for your help

read -a sentence reads a line of data from the STDIN file handle. The -a means it takes that data, splits it into words and stores the results in the sentence array.

read reads lines of data from STDIN. There needs to be something writing data to STDIN in order for read to get data out of STDIN. The unit tests do not write data to STDIN. If you try to read data from STDIN and there is no data there, the read blocks (waits) until there is data … or the tests time out.

Characters are fixed. You can’t increment the character “a”. “a” is “a”.

Are you trying to count occurrences of characters? You can use an associative array (dictionary, map) to count how many times you found a string (aka character when the string is one character long).

  • ((i++)) increments the integer variable i.
  • ((i + 1)) evaluates the value of whatever is stored in i plus one. It returns “true” if i + 1 is non-zero, ie if i is not -1.
  • (( ${assoc_array["some_string"]}++ )) increments the value of "some_string" in an assoc_array by one. You can use a variable in place of a fixed string, too: (( ${assoc_array[var]}++ ))

Hello,

Why doesn’t the while portion of this loop work correctly?

for char in "$@"                                           
 18 
 19 do
 20 
 21    while [[ "${char}" -ne "${alphabet[i]}" ]] 
 22 
 23                                            
 25        do
             echo "while test"
 26          (($i+1))
 27        done
 28  
 29 
 30     ((pangram_counter++))
 31      echo "for test"
 34 
 35 done

I’ve inserted echo statements in the body of the while loop, and in the for loop, just to see if these sections get run.

The while loop section never runs, but the for does.

I appreciate all the help I’ve gotten from all of you.

Where/how do you set alphabet? What do you expect this code to do? What do you expect char to equal?

Two more observations,

  • where do you reset i?
  • (($i+1)) does not alter i. You want ((i++)) or ((i += 1))

@IsaacG:

1.) "${alphabet[i]}" is based off of the array, alphabet
This code is an attempt at the pangram excercise.

2.) The while statement compares each character of char against each character of alphabet. While they’re not equal, i is supposed to be incremented to cycle through the alphabet array. When they are equal, pangram_counter is supposed to be incremented.

3.) char cycles through the input of the positional parameters, which I have used f e d c b a as a test.

@glennj:

I’ve tried to get i to increment, but i can’t get it to work. It’s supposed to cycle through the alphabet array; a-z, but as @IsaacG said, you can’t increment characters, so I’m not sure what to do.

alphabet=(a b c d e f g h i j k l m n o p q r s t u v w x y z)
  8 
 13 
 14 pangram_counter=0
 15 i=0
 16 
 17 for char in "$@"                                           
 18 
 19 do
 20 
 21    while [[ "${char}" -ne "${alphabet[i]}" ]]                   
 24        
 25        do
 26        echo "while test"
 27       ((i++))
 28        done
 29 
 30     ((pangram_counter++))
 31     echo "p_counter is $pangram_counter"
 32     echo "i is $i"
 33     echo "char is $char"
 34 
 35 done

Running a combination of set -x, and some debug statements, you can see that the while statement does make the intended comparison, char is getting the correct characters from the positional parameters, and the pangram_counter is being incremented correctly:

bash pangram f e d c b a
+ alphabet=(a b c d e f g h i j k l m n o p q r s t u v w x y z)
+ pangram_counter=0
+ i=0
+ for char in "$@"
+ [[ f -ne a ]]
+ (( pangram_counter++ ))
+ echo ' p_counter is 1'
 p_counter is 1
+ echo ' i is 0'
 i is 0
+ echo 'char is f'
char is f
+ for char in "$@"
+ [[ e -ne a ]]
+ (( pangram_counter++ ))
+ echo ' p_counter is 2'
 p_counter is 2
+ echo ' i is 0'
 i is 0
+ echo 'char is e'
char is e
+ for char in "$@"
+ [[ d -ne a ]]
+ (( pangram_counter++ ))
+ echo ' p_counter is 3'
 p_counter is 3
+ echo ' i is 0'
 i is 0
+ echo 'char is d'
char is d
+ for char in "$@"
+ [[ c -ne a ]]
+ (( pangram_counter++ ))
+ echo ' p_counter is 4'
 p_counter is 4
+ echo ' i is 0'
 i is 0
+ echo 'char is c'
char is c
+ for char in "$@"
+ [[ b -ne a ]]
+ (( pangram_counter++ ))
+ echo ' p_counter is 5'
 p_counter is 5
+ echo ' i is 0'
 i is 0
+ echo 'char is b'
char is b
+ for char in "$@"
+ [[ a -ne a ]]
+ (( pangram_counter++ ))
+ echo ' p_counter is 6'
 p_counter is 6
+ echo ' i is 0'
 i is 0
+ echo 'char is a'
char is a

Although i never increments, and the while loop section never runs.

What if you passed it a single argument like the tests do? You might find the output changes quite a bit!

Small observations:

  • -ne tests for inequality between numbers, the string equivalent is !=.
  • I guess it’s just for test but the exercise pass whole strings as argument to script, so for char in "$@" will not work.

OK, bash is weird with arithmetic. Because you’re using an arithmetic operator -ne bash evaluates the expression in an arithmetic context. In such a context, bash allows you to use variables without a $. This allows for tidier C-like arithmetic expressions.

But what bash is doing here is comparing the f variable to the a variable. And when an unset variable is used in an arithmetic expression, bash substitutes the value zero.

“zero -ne zero” is false, and the while loop is not entered.

Looking at the logic of your counters: suppose the first argument is z. You intend to increment i until “z” equals argument[i]. What happens for the next argument? What is i at the start of that loop iteration?

So after many failed attempts,

The logic of the loop works; I’ve looked at the output with a combination of echo statements and debugging mode, and everything works, accept for the input.

I can’t find a way to enter a sentence, and have the loop cycle through it char by char, and hopefully ignore spaces. I’ve tried Positional parameters, Read, and Substring Expansion {sentence:i:1}.

The best I can get is using Positional parameters, and feeding it individual chars, space by space; z y x w v u t... - that works perfectly.

  #!/bin/bash
  2 
  5 alphabet=(a b c d e f g h i j k l m n o p q r s t u v w x y z)
 11 pangram_counter=0
 12                                                          
 14 for char in $@                                               
 15 
 17   do
 18 
 19     i=0
 20                                                 
 22      while [[ "${char}" != "${alphabet[i]}" ]] 
 23                                                                 
 24             do
 28               ((i++))
 31             done
 32 
 33        ((pangram_counter++))
 34        
 38   done    
 41 
 45                                           
 46 if [[ "$pangram_counter" -eq "${#alphabet[@]}" ]]
         then
 49    echo "Your sentence IS a Pangram."
 50 
 51      else
 52    echo "Your sentence IS NOT a Pangram."
 53 
 54 fi

Does anyone have a suggestion on how to take the input as a natural sentence, spaces and all? Like the example given in the excercise “The quick brown fox jumped over the lazy dog.”

Thanks

Usually you’d loop through a string, one character at a time, using a loop and the substring expansion to extract substrings of length 1.

Entering the input at the prompt, and using for char in $@ with an echo $char in a loop only printed each individual word of the input.

Using read with for char in $sentence - did the same thing.

I then tried it using Substring Expansion:

What is it about this loop that won’t cycle through?

“the quick brown fox jumped over the lazy dog”

i=0
for char in "${sentence:i:1}"
 38 
 39  do
 40 
 41    echo "$char"
 42    ((i++))
 43    echo $i
 44 
 45  done

i gets to 1, t is printed, and won’t go any farther.

The "${sentence:i:1}" is expanded to "t" so you then have for char in "t"; do. That’s only one element to loop over.

It “won’t go any farther” because this

is not dynamic, in the sense that changing i after this line will not affect the loop.

Bash will expand that variable once, so you get

for char in t

What you need to do is use an arithmetic for loop to iterate i from 0 up to the input length, and pick the 1-character substring at that index.

for ((i = 0; i < ${#sentence}; i++)); do
  char=${sentence:i:1}
  ...
done

Turns out jhoney12 was a bot powered by an LLM :slight_smile: