web-dev-qa-db-fra.com

Créer un menu bash basé sur une liste de fichiers (mapper les fichiers sur des nombres)

Ceci la réponse montre un menu bash génial et intuitif où vous appuyez simplement sur le numéro et l’article sélectionné Mais c'est un peu gênant pour une liste de fichiers, car tout est codé en dur. Je préfère remplir mes fichiers dans une sorte de tableau, puis laisser l'utilisateur choisir un nombre qui correspond à nouveau au décalage de tableau.


Fondamentalement, voici ce que j'imagine:

Following `*.war` archives were found, select one:

  1) old.war
  2) debug.war
  3) release.war

Use number to select a file or 'stop' to cancel: blah
'blah' is not a number
Use number to select a file or 'stop' to cancel: 2
debug.war installed

Mais comment transformer une liste de fichiers dans ce tableau:

options=("Option 1" "Option 2" "Option 3" "Quit")

Comment obtenir une chaîne à un certain décalage dans options? Comment puis-je m'assurer que l'utilisateur est invité à réessayer? Puis-je autoriser la chaîne stop à arrêter le mode de sélection?

6
Tomáš Zato

Pour enregistrer les sorties de find dans un tableau bash, utilisez ceci:

unset options i
while IFS= read -r -d $'\0' f; do
  options[i++]="$f"
done < <(find /dir/ -maxdepth 1 -type f -name "*.war" -print0 )
  • read lit les entrées de find délimitées null (-d $'\0').
    • Le tableau $options est rempli avec les noms de fichiers.
  • find recherche uniquement les fichiers (-type f) dans le répertoire donné (-maxdepth 1) avec la fin .war (-name "*.war") et les imprime délimités par le caractère nul (-print0).

Le menu de sélection peut être fait comme ceci:

select opt in "${options[@]}" "Stop the script"; do
  case $opt in
    *.war)
      echo "War file $opt selected"
      # processing
      ;;
    "Stop the script")
      echo "You chose to stop"
      break
      ;;
    *)
      echo "This is not a number"
      ;;
  esac
done

Cela fonctionne comme suit:

1) /dir/old.war
2) /dir/debug.war
3) /dir/release.war
4) Stop the script
#? test
This is not a number
#? 2
War file /dir/debug.war selected
#? 4
You chose to stop
12
chaos

Vous pouvez également utiliser un glob Shell pour obtenir la liste des fichiers. Cette approche présente l’avantage de ne pas utiliser de programme externe (find) et de ne pas avoir besoin de restrictions sur le type de fichier (*war, par exemple) étant donné:

#!/usr/bin/env bash

## Collect the files in the array $files
files=( ~/foo/war/* )
## Enable extended globbing. This lets us use @(foo|bar) to
## match either 'foo' or 'bar'.
shopt -s extglob

## Start building the string to match against.
string="@(${files[0]}"
## Add the rest of the files to the string
for((i=1;i<${#files[@]};i++))
do
    string+="|${files[$i]}"
done
## Close the parenthesis. $string is now @(file1|file2|...|fileN)
string+=")"

## Show the menu. This will list all files and the string "quit"
select file in "${files[@]}" "quit"
do
    case $file in
    ## If the choice is one of the files (if it matches $string)
    $string)
        ## Do something here
        echo "$file"
        ## Uncomment this line if you don't want the menu to
        ## be shown again
        # break;
        ;;

    "quit")
        ## Exit
        exit;;
    *)
        file=""
        echo "Please choose a number from 1 to $((${#files[@]}+1))";;
    esac
done
7
terdon

selectpeut faire la plupart de ces tâches pour vous, sans trop d'efforts.

Comment transformer une liste de fichiers dans ce tableau?

Vous n'avez pas réellement besoin de. selectprend une série de mots à afficher en tant qu'options. Ceux-ci peuvent être donnés directement (select color in red green blue) ou proviennent de l'expansion d'un fichier glob (select file in *.war), ainsi que de développer un tableau en mots comme le fait l'exemple que vous avez trouvé (select option in "${options[@]}").

Comment puis-je obtenir une chaîne à un certain décalage dans les options?

selectle fait automatiquement et le stocke dans la variable que vous fournissez. (Si l'entrée de l'utilisateur n'est pas valide, il stocke la chaîne vide.)

Comment puis-je m'assurer que l'utilisateur est invité à réessayer?

Encore une fois, selectle fait pour vous, car select crée une boucle, comme whilename__. Il continuera à vous demander jusqu’à ce que vous ayez breaksorti de la boucle (ou jusqu’à ce qu’il lise EOF, généralement entré par Ctrl+D).

Puis-je autoriser la chaîne stopà arrêter le mode de sélection?

Oui, l'entrée de l'utilisateur est placée dans la variable REPLYname__, qu'elle corresponde ou non à l'une des options. Vous pouvez donc rechercher des valeurs spécifiques et les gérer différemment.

Mettre tous ensemble:

echo "The following `*.war` archives were found; select one:"

# set the Prompt used by select, replacing "#?"
PS3="Use number to select a file or 'stop' to cancel: "

# allow the user to choose a file
select filename in *.war
do
    # leave the loop if the user says 'stop'
    if [[ "$REPLY" == stop ]]; then break; fi

    # complain if no file was selected, and loop to ask again
    if [[ "$filename" == "" ]]
    then
        echo "'$REPLY' is not a valid number"
        continue
    fi

    # now we can use the selected file
    echo "$filename installed"

    # it'll ask for another unless we leave the loop
    break
done
4
deltab