如何在bash中读取可变数量的变量

如何在bash中读取可变数量的变量

我想从命令行读取多个字符串并读取厕所中的所有字符串。如何进一步在循环中处理(如打印)它们。抱歉,我是新手,请原谅我的新手问题。

#!/usr/bin/bash


echo "Enter the number of names of students"
read classcount

for ((i=1;i<=classcount;i++)); do
    read names[${i}]
done

for ((i=1;i<=classcount;i++)); do
    echo $names[${i}]
done

答案1

一般来说,最好避免脚本中的用户交互或从命令行使用的任何实用程序,因为它会阻碍自动化。

例如,您调用rm file1 file2 'file 3',并不rm只是期望它询问您要删除的文件数量,然后分别提示每个文件。

同样,在这里,最好列出学生来自命令行参数:

#! /usr/bin/bash -
classcount=$#
names=("$@")

if (( classcount > 0 )); then
  echo "Student${name[1]+s}:"
  printf ' - %s\n' "${name[@]}"
fi

并调用为:

that-script 'John Doe' 'Alice Smith' ...

或者,以文本文件中每行一个的学生列表为例(假设是 GNU xargs):

xargs -d '\n' -a student.list that-script

您还可以从标准输入中每行读取它们,但同样更好的是,无需交互并读取列表直到输入结束,而不是先提示用户进行计数:

#! /usr/bin/bash -

readarray -t names
classcount="${#names[@]}"

if (( classcount > 0 )); then
  echo "Student${name[1]+s}:"
  printf ' - %s\n' "${name[@]}"
fi

如果 stdin 是终端,用户将用 表示输入结束Ctrl+D

要从文件中获取列表:

that-script < student.list

read可用于从 stdin 读取一行,但语法是IFS= read -r lvalue, 不是read lvalue

另请注意,这[...]是一个通配符,因此应与出现在列表上下文中的所有参数扩展一样被引用。

另外,数组索引(Bourne 数组除外"$@")与bashin ksh(但与大多数其他 shell 不同)一样,从 0 开始,而不是 1。

因此,对于您的方法,语法应该是:

#! /usr/bin/bash -
PROGNAME=$0
die() {
  (( $# == 0 )) || printf >&2 '%s: ERROR: %s\n' "$PROGNAME" "$1"
  exit 1
}

IFS= read -rp "Enter the number of names of students: " classcount || die

# validate to avoid command injection vulnerability
shopt -s extglob # only needed in older versions of bash
[[ $classcount = @(0|[123456789]*([0123456789])) ]] ||
  die "Not a valid decimal integer number: $classcount"

for (( i = 1; i <= classcount; i++ )); do
  IFS= read -rp "Student $i: " 'names[i-1]' ||
    die "$(( classcount - i + 1 )) students missing"
done

for (( i=1; i <= classcount; i++)); do
  printf 'Student %s: %s\n' "$i" "${names[i-1]}"
done

$name[$i]在 (t)csh(在 70 年代末首次引入数组的 shell)中有效,尽管您需要$name[$i]:q确保空格或通配符在其中或 zsh 中没有被特殊处理,但在bash复制的ksh' s 阵列设计。

在这方面,ksh 努力向后兼容 Bourne shell,因此echo $name[$i]会像 Bourne shell 中一样输出与模式匹配的文件列表<contents-of-$name>[<contents-of-$i>]$name并且$i也会进行分割)。

在 ksh/bash 中,您需要${name[i]}语法(${name[$i]}或者${name[${i}]}也可以工作),并且像每个参数扩展一样,确保它被引用 ( "${name[i]}") 以防止 split+glob。

任何状况之下,echo不能用于输出任意数据

答案2

除了其他答案中所示的改进数据输入的各种方法之外,一个小的更改将使您的脚本按预期工作。

在行

echo $names[${i}]

外壳会膨胀$names,并${i}可能导致

[1]
[2]

等等,前提是names没有定义。否则它将names在左括号之前添加 的值。
(正如中提到的斯蒂芬·查泽拉斯' 注释,如果存在这样的文件,则由于缺少引号,结果[1]等可以替换为匹配的文件名等。)1

相反,使用

echo "${names[${i}]}"

编辑:

如中所述伊尔卡丘的评论,在问题的脚本中使用就足够了

echo "${names[i]}"

$因为索引是算术扩展,所以引用变量时不需要那里。

答案3

即使您想以交互方式请求列表,让脚本通过某些终止符检测列表的末尾可能会更加用户友好,而不是要求用户预先对它们进行计数并输入正确的计数。

(毕竟,计算机应该比普通人更准确地计算行数。仅当您使用具有手动内存管理功能的语言并且不想将数组大小调整为获得更多输入后的大小可能在内存有限的嵌入式环境中有意义,但在“完整”计算机上则没有那么多意义,并且在 shell 中绝对不是。)

相反,您可以要求用户通过输入空行(也可以是其他内容)来终止列表,并将收到的名称附加到数组中,只要您获得非空行即可。此外,如果在处理数据时实际上不需要索引,您可以使用 获取数组条目列表"${names[@]}",然后循环遍历:

#!/bin/bash
names=()
echo "Please enter the names of students (end with an empty line)"
while IFS= read -r name && [ "$name" ]; do
    names+=("$name")
done

echo "You entered ${#names[@]} name(s): "
for n in "${names[@]}"; do
    echo "$n"
done

[ "$name" ]测试输入的名称是否为空。如果看到文件结尾,循环也将结束。

相关内容