需要替代旧版本Bash上的脚本的读取数组/映射文件



脚本是:

#!/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]}";

较旧的系统没有mapfilereadarray,因此我需要将该行转换为可以将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

最新更新