I'm trying to create a shell script that can count words and lines in a file. (yes, I know you can do that using wc, but just for the sake of learning loops)
Code
echo "enter filename"
read f
if [ -s $f ]
then
terminal=`tty`
exec < $f
nol=0
now=0
while read line
do
nol=`expr $nol + 1`
set $line
now=`expr $now + $#`
done
echo "No.of lines in the file =$nol"
echo "No.of words in the file =$now"
exec < $terminal
fi
expected output:
No.of lines in the file =3
No.of words in the file =166
Actual output:
$ sh wc.sh
enter filename
spock.txt
CLUTTER_IM_MODULE='xim'
COLORTERM='gnome-terminal'
COMPIZ_BIN_PATH='/usr/bin/'
COMPIZ_CONFIG_PROFILE='ubuntu'
DBUS_SESSION_BUS_ADDRESS='unix:abstract=/tmp/dbus-PfcwnNxO9c'
DEFAULTS_PATH='/usr/share/gconf/ubuntu.default.path'
DESKTOP_SESSION='ubuntu'
DISPLAY=':0'
...
XDG_MENU_PREFIX='gnome-'
XDG_RUNTIME_DIR='/run/user/1000'
XDG_SEAT='seat0'
XDG_SEAT_PATH='/org/freedesktop/DisplayManager/Seat0'
XDG_SESSION_ID='c1'
XDG_SESSION_PATH='/org/freedesktop/DisplayManager/Session0'
XDG_VTNR='7'
XMODIFIERS='@im=ibus'
_='/bin/sh'
f='spock.txt'
line=''
nol='2'
now='66'
terminal='/dev/pts/0'
No.of lines in the file =3
No.of words in the file =166
Could anyone clarify what I'm doing wrong? thanks.
答案1
Probably for the last line, or somewhere in between, there's an empty line. When you run set
without arguments, it prints every variable set in the current shell. From the POSIX specification:
If no options or arguments are specified, set shall write the names and values of all shell variables in the collation sequence of the current locale.
This will throw off your calculation, since this does not set the arguments to the empty list - you'll be adding the previous line's word count again.
You're getting that output because $line
is empty, so what you should do is check if it is, in fact, empty:
while read line
do
nol=`expr $nol + 1`
if [ -n "$line" ]
then
set $line
now=`expr $now + $#`
fi
done
Aside from that, instead of exec <$f
, I'd suggest using:
while read ..
do
...
done < "$f"
What your doing now is:
terminal=`tty`
exec < $f
...
exec < $terminal
You are redirecting the whole script, whereas you only need the redirection for the loop. You can skip the call to tty
and the two exec
s:
if [ -s $f ]
then
nol=0
now=0
while read line
do
...
done < "$f"
...
fi
And use arithmetic expansion instead of command substitution with expr
:
nol=$((nol + 1))
now=$((now + $#))
And always, always, quote your variables unless you explicitly want splitting:
[ -s "$f" ]
exec < "$f"
Consider what happens when f
contains spaces:
f="there are spaces in this filename"
[ -s $f ]
exec < $f
You'll get errors, because once $f
is replaced by its contents, each word in it will be seen separately:
bash: [: too many arguments
bash: $f: ambiguous redirect