脚本是:
#!/bin/bash
# Dynamic Menu Function
createmenu () {
select selected_option; do # in "$@" is the default
if [ 1 -le "$REPLY" ] && [ "$REPLY" -le $(($#)) ]; then
break;
else
echo "Please make a vaild selection (1-$#)."
fi
done
}
declare -a drives=();
# Load Menu by Line of Returned Command
mapfile -t drives < <(lsblk --nodeps -o name,serial,size | grep "sd");
# Display Menu and Prompt for Input
echo "Available Drives (Please select one):";
createmenu "${drives[@]}"
# Split Selected Option into Array and Display
drive=($(echo "${selected_option}"));
echo "Drive Id: ${drive[0]}";
echo "Serial Number: ${drive[1]}";
较旧的系统没有mapfile
或readarray
,因此我需要将该行转换为可以将lsblk
输出的每一行读取到数组中的替代方法。
创建数组的相关行是:
mapfile -t drives < <(lsblk --nodeps -o name,serial,size | grep "sd");
您可以遍历输入并附加到数组中:
$ while IFS= read -r line; do arr+=("$line"); done < <(printf '%dn' {0..5})
$ declare -p arr
declare -a arr='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4" [5]="5")'
或者,对于您的具体情况:
while IFS= read -r line; do
drives+=("$line")
done < <(lsblk --nodeps -o name,serial,size | grep "sd")
请参阅 BashFAQ/001 以获得为什么IFS= read -r
是个好主意的出色解释:它确保空格是守恒的,反斜杠序列不会被解释。
这是我不久前想出的解决方案。这更好,因为它为不支持mapfile/readarray的旧版本的Bash提供了一个替代函数。
if ! type -t readarray >/dev/null; then
# Very minimal readarray implementation using read. Does NOT work with lines that contain double-quotes due to eval()
readarray() {
local cmd opt t v=MAPFILE
while [ -n "$1" ]; do
case "$1" in
-h|--help) echo "minimal substitute readarray for older bash"; exit; ;;
-r) shift; opt="$opt -r"; ;;
-t) shift; t=1; ;;
-u)
shift;
if [ -n "$1" ]; then
opt="$opt -u $1";
shift
fi
;;
*)
if [[ "$1" =~ ^[A-Za-z_]+$ ]]; then
v="$1"
shift
else
echo -en "${C_BOLD}${C_RED}Error: ${C_RESET}Unknown option: '$1'n" 1>&2
exit
fi
;;
esac
done
cmd="read $opt"
eval "$v=()"
while IFS= eval "$cmd line"; do
line=$(echo "$line" | sed -e "s#(["`])#\\1#g" )
eval "${v}+=("$line")"
done
}
fi
您不必稍微更改代码。它只是工作!
readarray -t services -u < <(lsblk --nodeps -o name,serial,size | grep "sd")
对于那些在家玩的人来说,这个旨在提供一个功能兼容Bash 5的mapfile
,但仍然可以追溯到Bash 3.x:
#!/usr/bin/env bash
if ! (enable | grep -q 'enable mapfile'); then
function mapfile() {
local DELIM="${DELIM-$'n'}"; opt_d() { DELIM="$1"; }
local COUNT="${COUNT-"0"}"; opt_n() { COUNT="$1"; }
local ORIGIN="${ORIGIN-"0"}"; opt_O() { ORIGIN="$1"; }
local SKIP="${SKIP-"0"}"; opt_s() { SKIP="$1"; }
local STRIP="${STRIP-"0"}"; opt_t() { STRIP=1; }
local FROM_FD="${FROM_FD-"0"}"; opt_u() { FROM_FD="$1"; }
local CALLBACK="${CALLBACK-}"; opt_C() { CALLBACK="$1"; }
local QUANTUM="${QUANTUM-"5000"}"; opt_c() { QUANTUM="$1"; }
unset OPTIND; local extra_args=()
while getopts ":d:n:O:s:tu:C:c:" opt; do
case "$opt" in
:) echo "${FUNCNAME[0]}: option '-$OPTARG' requires an argument" >&2; exit 1 ;;
?) echo "${FUNCNAME[0]}: ignoring unknown argument '-$OPTARG'" >&2 ;;
?) "opt_${opt}" "$OPTARG" ;;
esac
done
shift "$((OPTIND - 1))"; set -- ${extra_args[@]+"${extra_args[@]}"} "$@"
local var="${1:-MAPFILE}"
### Bash 3.x doesn't have `declare -g` for "global" scope...
eval "$(printf "%q" "$var")=()" 2>/dev/null || { echo "${FUNCNAME[0]}: '$var': not a valid identifier" >&2; exit 1; }
local __skip="${SKIP:-0}" __counter="${ORIGIN:-0}" __count="${COUNT:-0}" __read="0"
### `while read; do...` has trouble when there's no final newline,
### and we use `$REPLY` rather than providing a variable to preserve
### leading/trailing whitespace...
while true; do
if read -d "$DELIM" -r <&"$FROM_FD"
then [[ ${STRIP:-0} -ge 1 ]] || REPLY="$REPLY$DELIM"
elif [[ -z $REPLY ]]; then break
fi
(( __skip-- <= 0 )) || continue
(( COUNT <= 0 || __count-- > 0 )) || break
### Yes, eval'ing untrusted content is insecure, but `mapfile` allows it...
if [[ -n $CALLBACK ]] && (( QUANTUM > 0 && ++__read % QUANTUM == 0 ))
then eval "$CALLBACK $__counter $(printf "%q" "$REPLY")"; fi
### Bash 3.x doesn't allow `printf -v foo[0]`...
### and `read -r foo[0]` mucks with whitespace
eval "${var}[$((__counter++))]=$(printf "%q" "$REPLY")"
done
}
### Alias `readarray` as well...
readarray() { mapfile "$@"; }
fi
if [[ -z ${PS1+YES} ]]; then
echo "'mapfile' should only be called as a shell function; try "source ${BASH_SOURCE[0]##*/}" first..." >&2
exit 1
fi