2

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 Answer 1

0

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
9
  • thanks. I have few other questions -what did you mean by "splitting" in the last part. Could you explain that again?
    – TheWiz
    Mar 12, 2015 at 5:41
  • also, I cannot understand why I would need the : done < "$f" I wrote another script : pastebin.com/kj1aNg5q this gives the desired output in spite of using the same format here.
    – TheWiz
    Mar 12, 2015 at 5:43
  • @TheWiz see update.
    – muru
    Mar 12, 2015 at 6:45
  • thanks. could you answer my second comment above please. Also, I tried it your way. I still see the same output. pastebin.com/eCtjgwY6 @muru
    – TheWiz
    Mar 12, 2015 at 6:59
  • @TheWiz The redirection has nothing to do with the output you get. You're getting that output because $line is empty, so what you should do is check if it is, in fact empty.
    – muru
    Mar 12, 2015 at 7:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .