Ubuntu – Create bash script that allows you to choose multiple options instead of just one

bashscripts

I'm thinking about creating a bash script where multiple options can be specified and at the end define the variables according to the chosen options or execute certain orders when receiving the different options. An example is worth more than a thousand words:

[X] Copy only (1) - Options typed by the user
[ ] Move only (2)
[X] Checksum  (3) - Options typed by the user
[ ] Reset permission (4)
[ ] Exit (5)

Select choice:

I have found some options but I do not know how to make them do a certain function because I do not understand how the code works.

Update:

Code functional:

#!/bin/bash

#Contributing code by Sergiy Kolodyazhnyy and adaptation for MarianoM.

resize -s 40 90 > /dev/null #Change window size.

option=$(dialog --clear --backtitle "Synchronization..." --title "Synchronize" \
    --checklist "Select the synchronization options:"  20 50 10 \
       checksum "Compare the content" off \
       detail "Show more information" off \
       directory "Synchronize folders" on \
       recursive "Include subfolders" off 2>&1 > /dev/tty)

for i in $option; do #Set parameter of the menu options.
       case $i in
         checksum) c="-c" ;;
         detail) v="-v" ;;
         directory) d="-d" ;;
         recursive) r="-r" ;;
       esac
     done

if [ -z $option ]; then #Check if the variable is empty. If it is empty, it means that the user has not chosen an option.
 clear 
 echo
 echo "Error: No option has been selected or dialog not installed. The program can not continue."
 echo  
   else
 clear
   source=$(dialog --clear --backtitle "Please select source..." --title "Source:" --fselect "" 20 50 2>&1 > /dev/tty)
     if [ -z $source ]; then
       clear
       echo
       echo "Error program. Source not selected, try again!"
       echo
       exit
     fi
 clear
 destination=$(dialog --clear --backtitle "Please select destination..." --title "Destination:" --fselect "" 20 50 2>&1 > /dev/tty)
     if [ -z $destination ]; then
       clear
       echo
       echo "Error program. Destination not selected, try again!"
       echo
       exit
     fi
 clear
 rsync  "$c" "$v" "$d" "$r" "$source" "$destination"
 echo
fi
exit

Best Answer

From the discussion in the comments it is apparent that your main concern is to create as script with multiple selections, rather than focus on copying/moving files themselves, and the file operations are just an example. This functionality can be achieved via dialog command, which allows creating text user interfaces, with --checklist flag specifically; however there's nothing in the standard shell-only toolbox to achieve what you want. Hence, dialog is an appropriate tool for this job.

Below you will find an example script. While the script implements only 3 options that were discussed, it provides a decent starting point which users can extent further, and also addresses mutually exclusive options as mentioned in the comments. Particularly, the multiple selection is addressed in menu() function, which serves as a wrapper for dialog with --checklist option

To keep things simple, all you really need is this:

output=$(dialog --clear --backtitle "Backtitle. Use <SPACE> to select." --title "My Dialog" \
       --checklist "Select all that apply"  50 50 100 \
       checksum "SHA-256" off \
       copy "Copy only (exclusive with move)" off \
       move "Move only (exclusive with copy)" off 2>&1 > /dev/tty)

This saves the selection of multiple items to variable $output. Note that `2>&1 > /dev/tty) at the end are crucial to saving the return value into variable. But see the script below for more practical example:

#!/bin/bash

puke(){
    # function to exit with specific error message
    # analogous to 'die' in Perl
    printf ">>> Errors were encountered: %s\n" "$1" && exit
} > /dev/stderr

menu(){
    # dialog --help documents the option as follows:
    # --checklist    <text> <height> <width> <list height> <tag1> <item1> <status1>...
    # tags are what the output returns.
    # We can use word-splitting 
    # and iterate over output of this function in order. Of course first option
    # being checksum will always work and is not mutually exclusive with anything else
    dialog --clear --backtitle "Backtitle. Use <SPACE> to select." --title "My Dialog" \
           --checklist "Select all that apply"  50 50 100 \
           checksum "SHA-256" off \
           copy "Copy only (exclusive with move)" off \
           move "Move only (exclusive with copy)" off || puke

} 2>&1 1>/dev/tty

select_file(){
    dialog --backtitle "Choose file by typing or navigating and selecting with <SPACE>" --fselect "/etc/" \
           20 50  || puke
} 2>&1 1>/dev/tty


iter_actions(){
    # variables are available to child functions
    # Since we call iter_actions in main(), this
    # function also knows about main's variable $fselect

    for i ; do 
       case "$i" in
           checksum) sha256sum "$fselect" ;;
           copy) cp "$fselect" /tmp ;;
           move) mv "$fselect" /tmp ;;
       esac
    done
}

main(){
    # here I'm using /etc but you can use $PWD to default to user's 
    # current working directory, or accept positional parameters from command-line
    # as in $1, $2 and so forth
    fselect=$( select_file "/etc" )
    actions=$(menu) 
    printf "\r%b" "\033c" # this clear the screen

    case "$actions" in
       *copy*move|*move*copy) puke "Mutually exclusive options selected" ;;
        *) iter_actions $actions ;; # note here variable is unquoted on purpose
    esac
}

# script entry point
main "$@"

For further research:

Related Question