In general, user interaction in scripts or any utility used from the command line is best avoided as it hinders automation.
For instance, you call rm file1 file2 'file 3'
, not rm
alone expecting it to ask you the number of files to remove, and then prompting separately for each.
Similarly, here, it would be much better to take the list of students from command line arguments:
#! /usr/bin/bash -
classcount=$#
names=("$@")
if (( classcount > 0 )); then
echo "Student${name[1]+s}:"
printf ' - %s\n' "${name[@]}"
fi
And invoke as:
that-script 'John Doe' 'Alice Smith' ...
Or, to take the list of students one per line from a text file for instance (assuming GNU xargs
):
xargs -d '\n' -a student.list that-script
You could also read them one per line from standard input, but again better without interaction and reading the list until end-of-input instead of prompting the user for a count first:
#! /usr/bin/bash -
readarray -t names
classcount="${#names[@]}"
if (( classcount > 0 )); then
echo "Student${name[1]+s}:"
printf ' - %s\n' "${name[@]}"
fi
If stdin is a terminal, the user would signify end-of-input with Ctrl+D.
To get the list from a file:
that-script < student.list
read
can be used to read a line from stdin, but the syntax is IFS= read -r lvalue
, not read lvalue
.
Also note that [...]
is a globbing operator so should be quoted as well as all parameter expansions that appear in list context.
Also, array indices (other than for the "$@"
Bourne array) in bash
like in ksh
(but unlike most other shells), start at 0, not 1.
So for your approach, the syntax should rather be:
#! /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]
would be valid in (t)csh (the shell where arrays were first introduced in the late 70s), though you'd need $name[$i]:q
to make sure whitespace or wildcards are not treated specially in them, or in zsh, but not in bash
which copied ksh
's array design.
ksh tried hard to be backward compatible with the Bourne shell in that regard, so echo $name[$i]
would output the list of files matching the <contents-of-$name>[<contents-of-$i>]
pattern ($name
and $i
also subject to splitting) like in the Bourne shell.
In ksh/bash, you need the ${name[i]}
syntax (${name[$i]}
or ${name[${i}]}
also work), and like for every parameter expansion, make sure it's quoted ("${name[i]}"
) to prevent split+glob.
In any case, echo
can't be used to output arbitrary data.