[secret_handshake.sh] IFS not working

I’m already getting the correct results except that the field separator is still defaulting to space despite that I have specified IFS variable.

Can anyone spot anything on my code below?

#!/usr/bin/env bash

main () {

# [wink]="00001"
# [double blink]="00010"
# [close]="00100"
# [jump]="01000")

  declare -A secret=([1]="wink"
                     [2]="double blink"
                     [3]="close your eyes"
                     [4]="jump")
                    


    declare -a actions=()
    # Cyrus @ https://stackoverflow.com/questions/10278513/bash-shell-decimal-to-binary-base-2-conversion
    D2B=({0..1}{0..1}{0..1}{0..1}{0..1})

    declare -a index=()
    function find_bitmask_index() {
      param=$1

      for ((i=0,x=0; i<33; i=$((2**x)),x++ )) {
        IFS=","
        bit_index=$((param & $i))
        if [[ bit_index -gt 0 ]]; then
          #echo "bit index: $bit_index x: $x i: $i"
          #index+=( $bit_index )
          actions+=( ${secret[$x]} )
        fi
      }
    }

    find_bitmask_index $1
    echo "${actions[@]}"
}

main "$@"

What effect are you expecting the IFS to have and where? Are you trying to use it outside the loop to effect the echo command? Try echo "${actions[*]}". Also note you set IFS 32 times, which seems like overkill :blush:

Because the elements on my array are separated by space instead of a comma, so some tests fail. I tried moving the IFS around and still am not getting the desired result. I understand that setting IFS for each iteration is kinda silly, but it’s just one of the things I’ve tried. I put it back outside the for-loop but still not working as it should.

user@debian:~/programming/exercism.org/bash/secret-handshake$ bats secret_handshake.bats 
secret_handshake.bats
 ✓ wink for 1
 ✓ double blink for 10
 ✓ close your eyes for 100
 ✓ jump for 1000
 ✗ combine two actions
   (from function `assert_output' in file bats-extra.bash, line 394,
    in test file secret_handshake.bats, line 38)
     `assert_output "wink,double blink"' failed
   
   -- output differs --
   expected : wink,double blink
   actual   : wink double blink
   --
   
 ✗ all possible actions
   (from function `assert_output' in file bats-extra.bash, line 394,
    in test file secret_handshake.bats, line 45)
     `assert_output "wink,double blink,close your eyes,jump"' failed
   
   -- output differs --
   expected : wink,double blink,close your eyes,jump
   actual   : wink double blink close your eyes jump
   --
   

Here’s an interesting finding. So when I used ‘*’ I get comma-separated values vs when I use ‘@’, I get the space-separated values.

user@debian:~/programming/exercism.org/bash/secret-handshake$ ./secret_handshake.sh 1000
separated by space when using '@': jump close your eyes double blink wink
separated by comma when using '*': jump,close your eyes,double blink,wink

Therefore, using ‘*’ is the correct one here. Still, it’s interesting why IFS has no effect when with ‘@’ . Hmmm.

That’s the way it’s documented. 3.4.2 Special Parameters (talking about "$*" but it also applies to "${array[*]}"

When the expansion occurs within double quotes, it expands to a single word with the value of each parameter separated by the first character of the IFS special variable. That is, "$*" is equivalent to "$1c$2c…" , where c is the first character of the value of the IFS variable.

You can encapsulate this in a function

join() {
    local IFS=$1
    shift
    printf '%s\n' "$*"
}

a=(first second "the third one" fourth)
join : "${a[@]}"
# => first:second:the third one:fourth

See also Accessing bash command line args $@ vs $* - Stack Overflow

My script below passes all the tests. Although one thing is not quite clear to me because on the line close to the bottom

echo "${actions[*]}

I had to use * in order to get comma-separated values. I’m a bit confused though because I thought @ should be the correct one to use because the values should be treated as multiple tokens rather than a single string? Hence, my usage of @ except on the last one.

#!/usr/bin/env bash

function reverse_em() {
  temp=()
  IFS=',' param=( "$@" )
 
  for ((i=0, x= -1; i<${#param[@]}; i++, x--)) {
    temp+=("${param[x]}")
    
  }
  actions=( "${temp[@]}" )
}

main () {
 OLDIFS=$IFS
 IFS=',' 
  actions=()
   arg1=$1

  secret=('wink' 'double blink' 'close your eyes' 'jump')
  bitsize="${#secret[@]}"

  for ((i=1,x=1; x<bitsize+2; i=$((2**x)), x++ )) {
    bitvalue=$(printf '0x%02x' $((arg1 & i)))

    case $bitvalue in
      0x01)
        actions+=( "${secret[0]}" )
        ;;
      0x02)
        actions+=( "${secret[1]}" )
        ;;
      0x04)
        actions+=( "${secret[2]}" )
        ;;
      0x08)
        actions+=( "${secret[3]}" )
        ;;
      0x10)
        reverse_em "${actions[@]}"
        break
        ;;
    esac
  }

    echo "${actions[*]}"
    IFS="$OLDIFS"
}

main "$@"

The script is expected to output a single string in a specific format. This isn’t expanded the array to iterate over individual elements as data to be acted upon. This line is doing string formatting. As such, * is appropriate here.

Thanks for clarification. I’ve got to do some further reading on that.