Shell scripting : Change in input stream not working as desired

Shell scripting : Change in input stream not working as desired

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 execs:

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

相关内容