Cria um menu bash baseado na lista de arquivos (mapeia arquivos para números)

6

Esta resposta mostra um excelente e intuitivo menu de bash onde você simplesmente pressiona o número e o item é selecionado. Mas é meio inconveniente para a lista de arquivos, porque é tudo codificado. Eu prefiro preencher meus arquivos em algum tipo de matriz, então deixe o usuário escolher um número que mapeia novamente para o deslocamento do array.

Basicamente, isso é o que eu imagino:

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

Mas como faço para transformar a lista de arquivos nesta matriz:

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

Como faço para obter string em determinado deslocamento em options ? Como posso garantir que o usuário seja solicitado a tentar novamente? Posso permitir que a string stop pare o modo de seleção?

    
por Tomáš Zato 06.10.2015 / 12:53

3 respostas

12

Para salvar as saídas de find em uma matriz bash use isto:

unset options i
while IFS= read -r -d $'
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
' f; do options[i++]="$f" done < <(find /dir/ -maxdepth 1 -type f -name "*.war" -print0 )
  • read lê a entrada de find null delimitada ( -d $'$options' ).
    • O array find é preenchido com os nomes dos arquivos.
  • -type f procura apenas por arquivos ( -maxdepth 1 ) no diretório fornecido ( .war ) com final -name "*.war" ( -print0 ) e os imprime delimitados pelo caractere nulo ( %code% ).

O menu de seleção pode ser feito assim:

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

Funciona da seguinte forma:

unset options i
while IFS= read -r -d $'
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
' f; do options[i++]="$f" done < <(find /dir/ -maxdepth 1 -type f -name "*.war" -print0 )
    
por chaos 06.10.2015 / 13:24
7

Você também pode usar um shell glob para obter a lista de arquivos. Essa abordagem tem a vantagem de não usar um programa externo ( find ) e de não precisar de nenhuma restrição no tipo de arquivo ( *war , por exemplo) dado:

#!/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
    
por terdon 06.10.2015 / 15:12
4

select pode fazer a maior parte disso para você, sem muito esforço necessário.

How do I turn list of files into this array thingy?

Você realmente não precisa. select usa uma série de palavras para exibir como opções. Estes podem ser dados diretamente ( select color in red green blue ) ou vêm da expansão de um arquivo glob ( select file in *.war ), bem como expandir uma matriz em palavras como o exemplo que você encontrou ( select option in "${options[@]}" ).

How do I get string at certain offset in options?

select faz isso automaticamente e armazena na variável que você fornece. (Se a entrada do usuário é inválida, ele armazena a string vazia.)

How do I ensure user is asked to try again?

Novamente select faz isso para você, porque select faz um loop , como while . Ele continuará perguntando até você break fora do loop (ou até que leia EOF, normalmente inserido por Ctrl + D ).

Can I allow for string stop to stop selection mode?

Sim, a entrada do usuário é colocada na variável REPLY , independentemente de corresponder a uma das opções, para que você possa verificar valores específicos e lidar com eles de maneira diferente.

Juntando tudo:

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
    
por deltab 06.10.2015 / 17:53