Создать меню 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