Создать меню bash на основе списка файлов (сопоставить файлы с числами)

Этот ответ показывает большое и интуитивно понятное меню bash, где вы просто нажимаете номер и выбираете пункт. Но это немного неудобно для списка файлов, потому что все это жестко закодировано. Я бы предпочел заполнить мои файлы каким-нибудь массивом, а затем позволить пользователю выбрать число, которое снова отображается на смещение массива.


В основном это то, что я представляю:

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

Но как мне превратить список файлов в этот массив?

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

Как получить строку с определенным смещением в options? Как я могу убедиться, что пользователю предлагается повторить попытку? Могу ли я разрешить для строки stop остановить режим выбора?

3 ответа

Решение

Сохранить результаты find в bash массив использовать это:

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 читает входные данные из find с нулевым разделителем (-d $'\0').
    • Массив $options заполнен именами файлов.
  • find ищет только файлы (-type f) в данном каталоге (-maxdepth 1) с окончанием .war (-name "*.war") и печатает их, разделенные нулевым символом (-print0).

Меню выбора можно сделать так:

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

Это работает следующим образом:

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

Вы также можете использовать глобус оболочки, чтобы получить список файлов. Этот подход имеет то преимущество, что не использует внешнюю программу (find) и не требует каких-либо ограничений на тип файла (*warнапример) учитывая:

#!/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

select может сделать большую часть этого для вас, без особых усилий.

Как мне превратить список файлов в этот массив?

Вам на самом деле не нужно. select принимает ряд слов для отображения в качестве параметров. Это может быть дано напрямую (select color in red green blue) или исходить из расширения файла glob (select file in *.war), а также расширение массива на слова, как в примере, который вы нашли (select option in "${options[@]}").

Как получить строку с определенным смещением в настройках?

select делает это автоматически и сохраняет его в предоставленной вами переменной. (Если ввод пользователя недействителен, он сохраняет пустую строку.)

Как я могу убедиться, что пользователю предлагается повторить попытку?

Снова select делает это для вас, потому что выборка делает цикл, как while, Он будет продолжать спрашивать, пока вы break вне цикла (или до тех пор, пока не будет прочитано EOF, обычно вводится с помощью Ctrl+D).

Могу ли я разрешить для строки stop остановить режим выбора?

Да, пользовательский ввод помещается в переменную REPLYнезависимо от того, соответствует ли он одному из параметров, поэтому вы можете проверять конкретные значения и обрабатывать их по-разному.

Собираем все вместе:

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
Другие вопросы по тегам