IlChengis
IlChengis

Reputation: 23

Asking for user input in a loop until match found in array of command line arguments

Sometimes I need to find a specific serial in a box with many items, so I wrote a simple Bash script that allows me to use a barcode scanner to scan hundreds of barcodes until a match is found, at which point the screen flashes (so I can see it from the corner of my eyes while looking at the box).

The script works great, but it only checks against one specific serial number provided by the user. Here's the code:

#!/bin/bash

INPUT=''
SCAN=''
SN=''
I='0'

clear
printf "Enter serial\n"
read INPUT
SN=`printf "${INPUT}" | tr '[:lower:]' '[:upper:]'`

# Keep comparing scans to needed serial until a match is found
while [[ "${SCAN}" != *"${SN}"* ]];
    do
        clear
        printf "Looking for [ "${SN}" ]\n"
        printf "Please scan barcode\n"
        read INPUT
        SCAN=`printf "${INPUT}" | tr '[:lower:]' '[:upper:]'`
    done

# Flash screen when match is found
while [[ "${I}" -lt 3 ]];
    do
        printf '\e[?5h' && sleep 0.3
        printf '\e[?5l' && sleep 0.3
        I=$[${I}+1]
    done

printf "FOUND\n"

Today I spent hours trying to implement a way to pass multiple possible serial numbers as command line arguments, but I can't seem to get it working. I would like to be able to pass a small, manageable number of possible serials, like this:

$ ./script.sh sn1 sn2 sn3 sn4 sn5

And for the script continue asking for input until I come across the item I am looking for.

I've studied the handling of shell arguments, but I can't seem to "massage" the above while loop to get it to check if the scanned serial exists in the array (created from the command line arguments passed):

#!/bin/bash

snList=( "$@" )
INPUT=''
SCAN=''
SN=''
I='0'

clear

#displaying "things" so I can see what each variable contains (debugging)
printf "$@\n"
printf "$0\n"
printf "$*\n"
printf "$0\n"
printf "$1\n"
printf "$2\n"
printf "$3\n"
printf "snList: $snList\n"
printf "snList[@]: ${snList[@]}\n"
printf "snList[*]: ${snList[*]}\n"

# Keep comparing scans to needed serial until a match is found
while [[ ! " ${snList[*]} " =~ "${SCAN}" ]];
       do
               clear
               printf "Looking for [ "$*" ]\n"
               printf "Please scan barcode\n"
               read INPUT
               SCAN=`printf "${INPUT}" | tr '[:lower:]' '[:upper:]'`
       done

I've tried using ${snList[@]} in the loop as well, same result, it behaves like a match was found immediately, without even asking for a scan (indicating that the content of the while loop is not being executed).

Any help will be immensely appreciated, I think I am close, but I can't figure out what I am doing wrong.

Thanks in advance!

Upvotes: 1

Views: 464

Answers (2)

Jetchisel
Jetchisel

Reputation: 7831

Something like this maybe?

#!/usr/bin/env bash

to_compare_input=("$@")

exglob_pattern_input=$(IFS='|'; printf '%s' "@(${to_compare_input[*]})")

until [[ $user_input == $exglob_pattern_input ]]; do
  read -r user_input
done

Run the script with the the following arguments.

bash -x ./myscript foo bar baz more

Output

+ to_compare_input=("$@")
++ IFS='|'
++ printf %s '@(foo|bar|baz|more)'
+ exglob_pattern_input='@(foo|bar|baz|more)'
+ [[ '' == @(foo|bar|baz|more) ]]
+ read -r user_input
papa
+ [[ papa == @(foo|bar|baz|more) ]]
+ read -r user_input
mama
+ [[ mama == @(foo|bar|baz|more) ]]
+ read -r user_input
baz
+ [[ baz == @(foo|bar|baz|more) ]]

The first user input is empty since the builtinread has not been executed to ask for the user's input. As shown at the debug message.

+ [[ '' == @(foo|bar|baz|more) ]]

The second (assuming the user has entered papa) is papa

The third (assuming the user has entered mama) is mama

The last is baz which breaks out of off the until loop, because it belongs to the $extglob_pattern_input, which is an extglob feature.


A regex is also an alternative using the =~ operator.

#!/usr/bin/env bash

to_compare_input=("$@")

regex_pattern_input=$(IFS='|'; printf '%s' "^(${to_compare_input[*]})$")

until [[ $user_input =~ $regex_pattern_input ]]; do
  read -r user_input
done

Run the script same as before.


Using two loops which was suggested in the comments section.

#!/usr/bin/env bash

to_compare_input=("$@")

inarray() {
  local n=$1 h
  shift
  for h; do
    [[ $n == "$h" ]] && return
  done
  return 1
}

until inarray "$user_input" "${to_compare_input[@]}"; do
  read -r user_input
done

As for the tr if your version of bash supports the ^^ and ,, for uppercase and lowercase parameter expansion. use ${user_input^^}

until [[ ${user_input^^} == $exglob_pattern_input ]]; do

until [[ ${user_input^^} =~ $regex_pattern_input ]]; do

until inarray "${user_input^^}" "${to_compare_input[@]}"; do

Upvotes: 2

m0hithreddy
m0hithreddy

Reputation: 1839

Assuming no spaces in the bar code texts. You can do something like this

while read -r INPUT
do    
    #Append spaces to prevent substring matching
    if [[ $(echo " $@ " | grep -i " ${INPUT} " | wc -l) -eq 1 ]]  
    then
        break
    fi

done

Upvotes: 1

Related Questions