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' ...

または、テキストファイルから1行に1つずつ学生のリストを取得します(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

readstdinから1行を読み取るために使用できますが、構文はIFS= read -r lvalueisではありませんread lvalue

また、これは[...]ワイルドカードなので、リストのコンテキストに表示されるすべてのパラメータ拡張のように引用する必要があります。

また、配列インデックス(Bourne配列を除く"$@")は、bashinと同様にksh(しかし他のほとんどのシェルとは異なり)1ではなく0から始まります。

したがって、あなたの方法の構文は次のとおりです。

#! /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(1970年代後半に配列が最初に導入されたシェル)で動作しますが、コピーの配列設計である$name[$i]:qzshでスペースやワイルドカードが特に処理されないようにする必要があります。bashksh

この点に関して、ksh は Bourne シェルと逆互換性を持っているので、Bourne シェルと同様に、パターンと一致するecho $name[$i]ファイルのリストが出力<contents-of-$name>[<contents-of-$i>]$nameおよび分割)されます。$i

ksh / bashには構文が必要です${name[i]}${name[$i]}またはそれも機能します)、すべてのパラメータ拡張と同様に、分割+globを避けるために${name[${i}]}引用符で囲む必要があります。"${name[i]}"

いずれにせよ、echo任意のデータの出力には使用できません。

答え2

他の回答に示されているデータ入力を改善するさまざまな方法に加えて、小さな変更でスクリプトが期待どおりに機能します。

専門家

echo $names[${i}]

ケースが膨張して、次のような$names問題${i}が発生する可能性があります。

[1]
[2]

待って、names正義がないとしましょう。それ以外の場合は、names開いている括弧の前に値が追加されます。
(で述べたようにスティーブン・チャジェラス'コメント、そのようなファイルが存在する場合、結果[1]などは、引用符の欠落のために一致するファイル名などに置き換えることができます。 )1

代わりに

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

編集する:

で述べたようにイルカチョコメント、質問のスクリプトに使用するのに十分です。

echo "${names[i]}"

$インデックスは算術拡張であるため、変数を参照するときは必要ありません。

答え3

リストを対話的に要求する場合でも、ユーザーが事前にカウントを計算して正しい数を入力する必要があるよりも、スクリプトが一部のターミネーターを介してリストの終わりを検出できるようにする方がユーザーフレンドリーになる可能性があります。

(最終的に、コンピュータは通常の人よりも行をより正確に計算できるはずです。これは、手動メモリ管理機能を持つ言語を使用し、より多くの入力を受け取った後に配列のサイズを変更したくない場合にのみ可能です。メモリが限られている埋め込み環境では可能かもしれませんが、「フル」コンピュータではそうではなく、シェルではそうではありません。

代わりに、空白行(他の行も使用可能)を入力してリストを終了し、空でない行が表示されている限り、受信した名前を配列に追加するようにユーザーに要求できます。また、データを処理するときに実際にインデックスが必要ない場合は、配列項目のリストを取得してから"${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" ]入力した名前が空であることをテストします。ファイルの終わりが見える場合、ループも終了します。

関連情報