Home > OS >  How do I write a bash script that reads a list from a text file and takes user interaction for each
How do I write a bash script that reads a list from a text file and takes user interaction for each

Time:01-16

I'm writing a script to perform common tasks I like to do with a fresh install of Linux. It has functions for each phase from updating the system to installing common software.

Right now I'm trying to have the script read a list of software from a text file, ask the user if they would like to install it. If they say "yes" it would run apt to install that software. The current draft of the software has echo statements with the commands to avoid making changes while I test the script. Here is the function I'm trying to set up.

InstallAptSW () {
    file="./apps/apt-apps"

    while read -r line; do
        read -p "Would you like to install $line? [Y/n]" yn
        yn=${yn:-Y}
        case $yn in
            [Yy]* ) echo "sudo apt install -y $line";;
            [Nn]* ) printf "\nSkipping";
                    break;;
                * ) echo 'Please answer yes or no.';;
        esac
    done < $file
}

The apps file is just a list of software such as

code
gparted
snapd
neofetch
etc...

Here is the current result of the function:

$USER@$HOSTNAME:~/Documents/popOS-post-install$ ./PopOS-Post-Install.sh 
Please answer yes or no.
Please answer yes or no.
Please answer yes or no.
Please answer yes or no.
Please answer yes or no.
Please answer yes or no.
Please answer yes or no.

Goodbye

CodePudding user response:

I would use a new file descriptor for reading the file; that way you'll be able to read the user input inside the loop:
Edit: @LéaGris method is more standard, you should use it instead of mine. You can test my code though, I changed the behavior a little bit.

InstallAptSW () {
    local file="./apps/apt-apps"
    local fd appname yn
    exec {fd}< "$file"

    while IFS='' read -r appname <&$fd
    do
        while true
        do
            read -N 1 -p "Would you like to install '$appname'? [Y/n]" yn
            [[ $yn ]] && echo
            yn=${yn:-Y}
            case $yn in
                [Yy]) echo "sudo apt install -y $(printf %q "$appname")"
                      # sudo apt install -y "$appname"
                      break
                      ;;
                [Nn]) echo "skip"
                      break
                      ;;
            esac
        done
    done
    exec {fd}<&-
}

CodePudding user response:

Because the input for the while; do...done < "$file" code block is handled from the file containing the software names to install; the read -rp "Would you like to install $line? [Y/n]" yn which was not given a specific input handler, just inherits the file input from its outer code block.

Rather than reading user input, it reads (consumes) lines from the software list file.

Just adding <&1 to your read for user input should fix your script.

Here it is with a couple other fixes:

#!/usr/bin/env bash

InstallAptSW() {
  file='./apps/apt-apps'

  while read -r line; do
    # printf before read -r avoid using the read -p bashism
    printf 'Would you like to install %s [Y/n]? ' "$line"

    # Gives read the standard input handler
    # rather than having it inherit the file handler
    # from the outer while loop
    read -r yn <&1
    yn=${yn:-Y}
    case $yn in
      [Yy]*) echo sudo apt install -y "$line" ;;
      [Nn]*)
        # Single-quotes are better
        # when no expansion occurs within string
        printf '\nSkipping'
        break
        ;;
      *) echo 'Please answer yes or no.' ;;
    esac
  # Double quote the "$file" variable to prevent
  # word splitting and globing pattern matching
  done < "$file"
}

InstallAptSW
  •  Tags:  
  • Related