Having an issue with Bash exercise `bob.sh`

So I was doing the Bash exercise ‘bob.sh’. While ‘Test 21-multiple line question’ is passing fine on my local machine, but not exercism. The code is copy-pasted from my local pc.

Can I copy-paste my code in here so any one can have a look at it?

This is the bash version on my Fedora 38 machine:
GNU bash, version 5.2.15(1)-release (x86_64-pc-linux-gnu)

I’m new here but already learning a LOT and enjoying the exercises. Thanks a lot!

Yes! But make sure to code fence\wrap it with triple backticks (```), so that code like this:

root=“( dirname "( cd “$( dirname “$0” )” >/dev/null 2>&1 && pwd )” )"

will display like this:

root="$( dirname "$( cd "$( dirname "$0" )" >/dev/null 2>&1 && pwd )" )"

If you can, also paste in any error or failure messages you’re getting online.

:slight_smile:

Thanks @BethanyG

Here’s the error I’m getting at the website:

FAILED
Test 21
multiple line question

CODE RUN
run bash bob.sh $'\nDoes this cryogenic chamber make me look fat?\nNo'
assert_success
assert_output "Whatever."
TEST FAILURE
(from function `assert_output' in file bats-extra.bash, line 394,
 in test file bob.bats, line 150)
  `assert_output "Whatever."' failed

-- output differs --
expected : Whatever.
actual   : Fine. Be that way!
--

Here’s the same code I’m using on my machine:

#!/usr/bin/env bash

declare -A RESPONSE_HASH=([sure]="Sure." [chill_out]="Whoa, chill out!" [calm_down]="Calm down, I know what I'm doing!" [fine]="Fine. Be that way!" [whatever]="Whatever.")
RESPONSE=
WEIGHT=0

read -r STATEMENT <<< "$1"
read -ra ARRAY <<< "$1"

INDEX=${#STATEMENT}
if [[ ${#STATEMENT} -ne 0 ]]; then
  for ((i=0; i<$((INDEX-1)); i++)) {
      if [[ ${STATEMENT:i:1} =~ [[:lower:]] ]]; then
          if [[ ${STATEMENT:i+1:1} =~ [[:punct:][:digit:][:space:]] ]]; then
            WEIGHT=2
            break
          fi

          WEIGHT=4
       elif [[ ${STATEMENT:i:1} =~ [[:upper:]] ]]; then
          if [[ ${STATEMENT:i+1:1} =~ [[:lower:]] ]]; then
            WEIGHT=4
            break
          fi
          WEIGHT=7
       elif [[ ${STATEMENT:i:1} =~ [[:punct:][:digit:][:space:]] ]]; then
         if [[ $WEIGHT != 0 ]]; then
           WEIGHT=$WEIGHT
         else
           WEIGHT=2
         fi
      fi
    }
fi

if [[ ${STATEMENT: -1:1} == '?' ]]; then
  WEIGHT=$((WEIGHT+1))
fi

case $WEIGHT in
  0)
    RESPONSE="${RESPONSE_HASH[fine]}"
    ;;
  1|3|5)
    RESPONSE="${RESPONSE_HASH[sure]}"
    ;;
  2|4|6)
    RESPONSE="${RESPONSE_HASH[whatever]}"
    ;;
  7)
    RESPONSE="${RESPONSE_HASH[chill_out]}"
    ;;
  8)
    RESPONSE="${RESPONSE_HASH[calm_down]}"
    ;;
esac

echo "$RESPONSE"
user@pc:~/programming/exercism.org/bash$ ./bob.sh '\nDoes this cryogenic chamber make me look fat?\nNo'
Whatever.

Note, '\n' and $'\n' are not the same thing. The former is a literal \ char and a literal n char. The latter is a newline char. bash has a special form of $'..' quoting which expands escape sequences. Your local test and the bats unit test differ.

Hi @IsaacG Thanks for the explanation. At first, I thought that only those surrounded by single quotes are part of Argument 1.

I retested it again and am now getting the same result as with the website. Will recheck.

Cheers.

I think I’m getting close on this one. I have one more question regarding Test 23.

Shouldn’t the answer for this is Whatever. since the sentence actually ended with a space and not a question mark? I pasted my new code below:

FAILED

Test 23

ending with whitespace

![](https://assets.exercism.org/assets/icons/chevron-down-5ae28e42ee217bae38f4eb1c119cafd0301dd5f6.svg)

### CODE RUN

\```
run bash bob.sh 'Okay if like my  spacebar  quite a bit?   '
assert_success
assert_output "Sure."
\```

### TEST FAILURE

(from function `assert_output' in file bats-extra.bash, line 394, in test file bob.bats, line 164) `assert_output "Sure."' failed -- output differs -- expected : Sure. actual : Whatever. --
#!/usr/bin/env bash


   main () {
declare -A RESPONSE_HASH=([sure]="Sure."
          [chill_out]="Whoa, chill out!"
          [calm_down]="Calm down, I know what I'm doing!"
          [fine]="Fine. Be that way!"
          [whatever]="Whatever.")

RESPONSE=
WEIGHT=0x00

read -r STATEMENT <<< "$1"
read -ra ARRAY <<< "$1"

INDEX=${#1}
if [[ ${#1} -ne 0 ]]; then
  for ((i=0; i<$((INDEX-1)); i++)) {
    if [[ ${1:i:1} =~ [[:space:]] && ${1: i+1:1} =~ [[:space:]] ]]; then
      #echo "before: space-space $WEIGHT"
        #WEIGHT=$(printf "0x%02x" "0x00")
        WEIGHT=$((WEIGHT | 0x00))
      #echo "after: space-space ${1:i:1} $WEIGHT"
    elif [[ ${1:i:1} =~ [[:space:]] && ${1: i+1:1} =~ [^[:space:]] ]]; then
      #echo "before: space-not-space ${1:i:1} ${1: i+1:1} $WEIGHT"
        WEIGHT=$((WEIGHT | 0x02))
      #echo "after: space-not-space $WEIGHT"
    elif [[ ${1:i:1} =~ [[:digit:][:punct:]] && ${1: i+1:1} =~ [[:digit:][:punct:]] ]]; then
      #echo "before: digit-punct $WEIGHT"
        #WEIGHT=$(printf "0x%02x" "0x04")
        WEIGHT=$((WEIGHT | 0x04))
      #echo "after: digit-punct $WEIGHT"
        #WEIGHT=$((WEIGHT | 0x04))
    #elif [[ ${1:i:1} =~ [[:digit:][:punct:]] && ${1: i+1:1} =~ [^[:digit:][:punct:]] ]]; then
    #    WEIGHT=$((WEIGHT | 0x08))
    elif [[ ${1:i:1} =~ [[:lower:]] ]]; then
      #echo "before: lower $WEIGHT"
      WEIGHT=$((WEIGHT | 0x08))
      #echo "after: lower $WEIGHT"
    elif [[ ${1:i:1} =~ [[:upper:]] ]]; then
      #echo "before: upper ${1:i:1} - $WEIGHT"
      WEIGHT=$((WEIGHT | 0x10))
      #echo "after: upper ${1:i:1} - $WEIGHT"
    fi
  }

  WEIGHT=$(printf "0x%02x" "$WEIGHT")

  if [[ ${1: -1:1} == '?' ]]; then
    WEIGHT=$(printf "0x%02x" "$((WEIGHT + 0x01))")
  fi
fi

#echo "weight: $WEIGHT -- ${#1}"
case $WEIGHT in
  0x05|0x07|0x07|0x09|0x0d|0x1b|0x1f)
    RESPONSE="${RESPONSE_HASH[sure]}"
    ;;
  0x00)
    RESPONSE="${RESPONSE_HASH[fine]}"
    ;;
  0x06|0x0e|0x1a)
    RESPONSE="${RESPONSE_HASH[whatever]}"
    ;;
  0x10|0x12|0x16)
    RESPONSE="${RESPONSE_HASH[chill_out]}"
    ;;
  0x13)
    RESPONSE="${RESPONSE_HASH[calm_down]}"
    ;;
esac

echo "$RESPONSE"
   }

main "$@"

I believe the intent here is that trailing whitespace should not change the meaning of the input.

I’ve finally cracked it! Wow, this is not an easy task, for me at least. I implemented my solution using the native features available to bash and did not use any external tools. I’m not sure if that is the intention here.

How can I mark this as solved?

#!/usr/bin/env bash
shopt -s extglob  

main () {
    declare -A RESPONSE_HASH=([sure]="Sure."
              [chill_out]="Whoa, chill out!"
              [calm_down]="Calm down, I know what I'm doing!"
              [fine]="Fine. Be that way!"
              [whatever]="Whatever.")
    RESPONSE=
    WEIGHT=0x00
    PARAM="$1"
    PARAM=$(printf '%s\n' "${PARAM%%+([[:space:]])}")
    PARAM=$(printf '%s\n' "${PARAM##+([[:space:]])}")

    if [[ ${#1} -ne 0 ]]; then
      for ((i=0; i<${#1}-1; i++)) {
        if [[ ${PARAM:i:1} =~ [[:space:]] && ${PARAM: i+1:1} =~ [[:space:]] ]]; then
            WEIGHT=$((WEIGHT | 0x00))
        elif [[ ${PARAM:i:1} =~ [[:space:]] && ${PARAM: i+1:1} =~ [^[:space:]] ]]; then
            WEIGHT=$((WEIGHT | 0x02))
        elif [[ ${PARAM:i:1} =~ [[:digit:][:punct:]] && ${PARAM: i+1:1} =~ [[:digit:][:punct:]] ]]; then
            WEIGHT=$((WEIGHT | 0x04))
        elif [[ ${PARAM:i:1} =~ [[:lower:]] ]]; then
          WEIGHT=$((WEIGHT | 0x08))
        elif [[ ${PARAM:i:1} =~ [[:upper:]] ]]; then
          WEIGHT=$((WEIGHT | 0x10))
        fi
      }

      WEIGHT=$(printf "0x%02x" "$WEIGHT")

      if [[ ${PARAM: -1:1} == '?' ]]; then
        WEIGHT=$(printf "0x%02x" "$((WEIGHT + 0x01))")
      fi
    fi
     
    #echo "weight: $WEIGHT -- ${#PARAM}"
    case $WEIGHT in
      0x05|0x07|0x09|0x0d|0x1b|0x1f)
        RESPONSE="${RESPONSE_HASH[sure]}"
        ;;
      0x00)
        RESPONSE="${RESPONSE_HASH[fine]}"
        ;;
      0x06|0x0c|0x0e|0x18|0x1a)
        RESPONSE="${RESPONSE_HASH[whatever]}"
        ;;
      0x10|0x12|0x16)
        RESPONSE="${RESPONSE_HASH[chill_out]}"
        ;;
      0x13)
        RESPONSE="${RESPONSE_HASH[calm_down]}"
        ;;
    esac

    echo "$RESPONSE"
}

main "$@"

This is an awesome website, I should have discovered this earlier!

Thanks @IsaacG and @BethanyG for the guidance!

2 Likes

One more thing: the bats tool does not seem to work on my end here, so I just copy-pasted my code into the web ui.

user@debian:~/programming/exercism.org/bash/bob$ /home/user/bin/bats-core-1.10.0/bin/bats bob.bats 
bob.bats
 ✓ stating something
 - shouting (skipped)
 - shouting gibberish (skipped)
 - asking a question (skipped)
 - asking a numeric question (skipped)
 - asking gibberish (skipped)
 - talking forcefully (skipped)
 - using acronyms in regular speech (skipped)
 - forceful question (skipped)
 - shouting numbers (skipped)
 - no letters (skipped)
 - question with no letters (skipped)
 - shouting with special characters (skipped)
 - shouting with no exclamation mark (skipped)
 - statement containing question mark (skipped)
 - non-letters with question (skipped)
 - prattling on (skipped)
 - silence (skipped)
 - prolonged silence (skipped)
 - alternate silence (skipped)
 - multiple line question (skipped)
 - starting with whitespace (skipped)
 - ending with whitespace (skipped)
 - other whitespace (skipped)
 - non-question ending with whitespace (skipped)
 - no input is silence (skipped)
 - yelling a filename expansion (skipped)
 - asking a filename expansion (skipped)

28 tests, 0 failures, 27 skipped

See “Skipped tests” on the bash testing docs, Testing on the Bash track | Exercism's Docs

read is not way to store the input parameter into a variable. If the input is multi-line, this only captures the first line. Consider this:

$ set -- $'first line\nsecond line'
$ read -r STATEMENT <<< "$1"
$ declare -p STATEMENT
declare -- STATEMENT="first line"

This would account for the $'\nDoes this cryogenic chamber make me look fat?\nNo' test failure: your code read the (empty) line before the first newline and returned the “silence” response.

(declare -p varname is a very handy way to dump the exact contents of a variable.)


Get out of the habit of using ALLCAPS variable names, leave those as reserved by the shell. One day you’ll write PATH=something and then wonder why your script is broken.

If you’re using ALLCAPS to indicate a constant, use the readonly or declare -r builtins to let the shell know too.

[quote="turtle666, post:3, topic:7219"]
`read -r STATEMENT <<< "$1"`
[/quote]

Wow! I didn’t know this. Also good advice on avoiding all caps in my variables.

Thanks a lot @glennj . This site is truly amazing. I’ve only been here for a few days and already seeing my flaws.

Surely you mean “already seeing opportunities to learn and improve” :blue_heart: None of us are perfect, and we all learn from each other.

3 Likes