SlayTheMonkey
SlayTheMonkey

Reputation: 33

What is the best way to accept a 2nd user input from options defined by the 1st user input?

My background is in SQL but I've been learning Bash to create tools to help non-Linux users find what they need from my Linux system - I am pretty green with Bash, I apologize if this looks a bit dumb.

The goal of the script is to essentially display all directories within the current directory to the user, and allow them to input 1-9 to navigate to lower directories.

My sticking point is that I'm trying to use arrays to define potential filepaths, since in practice new directories will be added over time and it is not practical to edit the script each time a filepath is added.

Here's my prototype so far, currently it navigates into Test1, Test2, or Test3 then echos pwd to prove it is there.

#Global Variables
DIR_MAIN='/home/admin/Testhome'

#Potential Filepaths
#/home/admin/Testhome/Test1/Test1-1/
#/home/admin/Testhome/Test1/Test1-2/
#/home/admin/Testhome/Test2/Test2-1/
#/home/admin/Testhome/Test2/Test2-2/
#/home/admin/Testhome/Test3/Test3-1/
#/home/admin/Testhome/Test3/Test3-2/

#Defining Array for first user input
arr=($(ls $DIR_MAIN))

#System to count total number of directories in filepath, then present to user for numbered selection

cnt=0
for i in ${arr[@]}
do
    cnt=$(($cnt+1))
    echo "$cnt) $i"
done

read -p "Select a folder from the list: " answer
case $answer in
    1)
    cd $DIR_MAIN/${arr[0]}
        echo "Welcome to $(pwd)"
    ;;
    2)
        cd $DIR_MAIN/${arr[1]}
        echo "Welcome to $(pwd)"
    ;;
    3)
    cd $DIR_MAIN/${arr[2]}
        echo "Welcome to $(pwd)"
    ;;
esac

I've tried the following, but it doesn't like the syntax (to someone experienced I'm sure these case statements look like a grenade went off in vim).

I'm beginning to wonder if the SELECT CASE road I'm going down is appropriate, or if there is an entirely better way.

#User Input Start
echo "What is the secret number?"
while :
do
    read STRING1
    case $STRING1 in
    1)
        echo "Enter the number matching the directory you want and I will go there"
        echo "1 - ${arr[0]}"
        echo "2 - ${arr[1]}"
        echo "3 - ${arr[2]}"
            read STRING2
            case $STRING2 in
                1) 
                cd $DIR_MAIN/${arr[0]}
                echo "Welcome to" $(pwd)
                2) 
                cd $DIR_MAIN/${arr[1]}
                echo "Welcome to" $(pwd)
                3)
                cd $DIR_MAIN/${arr[2]}
                echo "Welcome to" $(pwd)
                *)
                echo "Thats not an option and you know it"
    *)
        echo "1 is the secret number, enter 1 or nothing will happen"
        ;;
    esac
    #break needs to be down here somewhere
done

Ultimately I know I'll need to variabilize a local array once I'm in Test2 for example (since in practice, this could descend as far as /Test2/Test2-9 and there would be tons of redundant code to account for this manually).

For now, I'm just looking for the best way to present the /Test2-1 and /Test2-2 filepaths to the user and allow them to make that selection after navigating to /Test2/

Upvotes: 2

Views: 66

Answers (2)

Jetchisel
Jetchisel

Reputation: 7791

This might do what you wanted.

#!/usr/bin/env bash

shopt -s nullglob

n=1
for i in /home/admin/Testhome/Test[0-9]*/*; do
  printf '%d) %s\n' "$n" "$i"
  array[n]="$i"
  ((n++))
done

(( ${#array[*]} )) || {
   printf 'It looks like there is/are no directory listed!\n' >&2
   printf 'Please check if the directories in question exists!\n' >&2
   return 1
 }

dir_pattern_indices=$(IFS='|'; printf '%s' "@(${!array[*]})")

printf '\n'
read -rp "Select a folder from the list: " answer

if [[ -z $answer ]]; then
  printf 'Please select a number and try again!' >&2
  exit 1
elif [[ $answer != $dir_pattern_indices ]]; then
  printf 'Invalid option %s\n'  "$answer" >&2
  exit 1
fi

for j in "${!array[@]}"; do
  if [[ $answer == "$j" ]]; then
    cd "${array[j]}" || exit
    printf 'Welcome to %s\n' "$(pwd)"
    break
  fi
done

The script needs to be sourced e.g.

source ./myscript

because of the cd command. See Why can't I change directory using a script.


Using a function instead of a script.

Let's just name the function list_dir

list_dir() {

  shopt -s nullglob

  declare -a array
  local answer dir_pattern_indices i j n

  n=1
  for i in /home/admin/Testhome/Test[0-9]*/*; do
    printf '%d) %s\n' "$n" "$i"
    array[n]="$i"
    ((n++))
  done

  (( ${#array[*]} )) || {
    printf 'It looks like there is/are no directory listed!\n' >&2
    printf 'Please check if the directories in question exists!\n' >&2
    return 1
  }

  dir_pattern_indices=$(IFS='|'; printf '%s' "@(${!array[*]})")

  printf '\n'
  read -rp "Select a folder from the list: " answer

  if [[ -z $answer ]]; then
    printf 'Please select a number and try again!' >&2
    return 1
  elif [[ $answer != $dir_pattern_indices ]]; then
    printf 'Invalid option %s\n'  "$answer" >&2
    return 1
  fi

  for j in "${!array[@]}"; do
    if [[ $answer == "$j" ]]; then
      cd "${array[j]}" || return
      printf 'Welcome to %s\n' "$(pwd)"
      break
    fi
  done
}

All of the array names and variables are declared local to the function in order not to pollute the interactive/enviromental shell variables.

Put that somewhere in your shellrc file, like say in ~/.bashrc then source it again after you have edited that shellrc file.

source ~/.bashrc

Then just call the function name.

list_dir

Upvotes: 1

SlayTheMonkey
SlayTheMonkey

Reputation: 33

I took what @Jetchisel wrote and ran with it - I see they updated their code as well.

Between that code and what I hacked together piggybacking off what he wrote, I'm hoping future viewers will have what they need to solve this problem!

My code includes a generic logging function (can write to a log file if you define it and uncomment those logging lines, for a script this size I just use it to output debugging messages), everything below is the sequence used.

As he mentioned the "0" element needs to be removed from the array for this to behave as expected, as a quick hack I ended up assigning array element 0 as null and adding logic to ignore null.

This will also pull pretty much anything in the filepath, not just directories, so more tweaking may be required for future uses but this serves the role I need it for!

Thank you again @Jetchisel !

#hopt -s nullglob
DIR_MAIN='/home/admin/Testhome'
Dir_Cur="$DIR_MAIN"
LOG_LEVEL=1
array=(NULL $(ls $DIR_MAIN))

########FUNCTION LIST#########
####Generic Logging Function
Log_Message()
{
local logLevel=$1
local logMessage=$2
local logDebug=$3
local dt=$(date "+%Y-%m-%d %T")
##Check log level
if [ "$logLevel" == 5 ]
then    local logLabel='INFO'
elif [ "$logLevel" == 1 ]
then    local logLabel='DEBUG'
elif [ "$logLevel" == 2 ]
then    local logLabel='INFO'
elif [ "$logLevel" == 3 ]
then    local logLabel='WARN'
elif [ "$logLevel" == 4 ]
then    local logLabel='ERROR'
fi
##Check conf log level
if [ "$LOG_LEVEL" == 1 ]
then    #echo "$dt  [$logLabel] $logMessage" >> $LOG_FILE ##Log Message
    echo "$dt   [$logLabel] $logMessage" ##Echo Message to Terminal 
    ##Check if Debug Empty
    if [ "$logDebug" != "" ]
    then    #echo "$dt  [DEBUG] $logDebug" >> $LOG_FILE ##Extra Debug Info
        echo "$dt   [DEBUG] $logDebug" ##Extra Debug Info
    fi
elif [ "$logLevel" -ge "$LOG_LEVEL" ]
then    #echo "$dt  [$logLabel] $logMessage" >> "$LOG_FILE" ##Log Message
    echo "$dt   [$logLabel] $logMessage"
fi
} 
####
####Function_One
##Removes 0 position in array by marking it null, generates [1-X] list with further filepaths
Function_One()
{
Log_Message "1" "entered Function_One"
local local_array=("$@")
Log_Message "1" "${local_array[*]}"
n=1
for i in "${local_array[@]}"; do
  if [ "$i" != "NULL" ]
  then
  printf '%d) %s\n' "$n" "$i"
  array[n]="$i"
  ((n++))
  fi
done

printf '\n'
read -rp "Select a folder from the list: " answer

for j in "${!local_array[@]}"; do
  if [[ $answer == "$j" ]]; then
    cd "$Dir_Cur/${local_array[j]}" || exit
    printf 'Welcome to %s\n' "$(pwd)"
    break
  fi
done
}
####

########FUNCTION LIST END#########

########MAIN SEQUENCE########
echo "Script start"
Function_One "${array[@]}"
Dir_Cur="$(pwd)"
array2=(NULL $(ls $Dir_Cur))
Function_One "${array2[@]}"
Dir_Cur="$(pwd)"
$Dir_Cur/test_success.sh
echo "Script end"
########

Upvotes: 1

Related Questions