Sorry if it's long and confusing, but I have three files, namely cat, dog, and cow.

My code is something like this:

echo -e "Enter file name: \c"  
read filename                   #saves variable 
if [ -e "$filename" ]
total =$(wc -l $filename)       #calculate total lines in the*CAT* file 
echo There are $total         #prints There are *numberoflines*
echo $total > newfile           #writes the answer into a newly created file
echo $filename not found

When I run the above code, the following happens:

  Enter file name : cat
  There are 5 cat 

(I input cat at the prompt and I see the number of lines in the file.)

It will give me an output of 5 cat when I run the file called cat which has 5 lines in it.

But let's say when I want to run all three files at the same time, what do I have to do?

Let's say I want it to display as following

Enter file name : cat dog cow 
5 cat 
2 dog
14 cow
Total 21

Unfortunately, it displays

file not found

when I run all files at the same time, but if I run the script on the files one by one, nothing is wrong.

  • Please fix your shell script. (1) Quote your variables, (2) Remove space at total =.... (3) You're missing a fi. – pLumo May 18 at 8:45
  • Of course it does not work. Your command is [ -e "cat dog cow" ], but a file with name "cat dog cow" does not exist. – pLumo May 18 at 8:47
  • I tried to run your script with tcsh as you tagged but I get -e Enter file name: read: Command not found. filename: Undefined variable. – Arkadiusz Drabczyk May 18 at 8:50
  • Your script does not display file not found when run with the tcsh shell. It says -e Enter file name: \c followed by the error read: Command not found.. If you run it with bash, it terminates with script: line 10: syntax error: unexpected end of file. What shell are you actually using? – Kusalananda May 18 at 8:59
  • 1
    @WenhanXiao YOur script still contains errors as already written in comments. Consider using shellcheck.net to check your script. In case you re-typed your script, please copy&paste exactly the script you run on your system and copy&paste the exact input/output/error messages. Instead of using the read command to get the file name(s) I suggest to use command line arguments. You could run your script as linecounter.sh cat dog cow and implement it like for filename in "$@"; do ... ; done – Bodo May 18 at 9:26

Apart from the syntax issues already mentioned in comments, your main issue with the code is that you are reading what you perceive to be multiple filenames into a single string, then you use that single string as a single filename.

Instead of correcting that aspect of the script, let's rethink what it is that you want to do.

You want the user to supply one or several filenames, then you want your script to count the number of lines in each and output the number of lines in each file, along with the total number of lines. You also want to allow the user to store this information to an output file. If a filename does not exist, you want the user to by made aware of this.

The script could look like this:

#!/bin/sh -

wc -l -- "$@"

Testing this without storing the output:

$ ./script.sh cat dog cow
       5 cat
       2 dog
      14 cow
      21 total

Calling the script with a filename that does not exist:

$ ./script.sh cat dog cow hamster
       5 cat
       2 dog
      14 cow
wc: hamster: No such file or directory
      21 total

Storing the output in a file:

$ ./script.sh cat dog cow hamster >output
wc: hamster: No such file or directory
$ cat output
       5 cat
       2 dog
      14 cow
      21 total

The "$@" in the script will be replaced by the list of command line arguments, each individually quoted. This means that if you call your script with cat, dog and cow on the command line, the "$@" in the script will expand to that list.

The wc utility already performs all the necessary things, including providing you with the grand total across all the given files, so all we need to do is to call it with its -l option on the filenames listed on the script's command line to get it to count lines.

The output is written to standard output by default. This gives the user of script the freedom to easily redirect the output to wherever they want.

Also notice that it's far more convinient to give a script command line arguments, using tab completion or filename globbing patterns, than to write each filename in by hand at an interactive prompt. In this case, it also greatly simplifies the script's code.

What you may want to do as an extra check, is to complain to the user if the did not supply any filenames. The wc utility will otherwise start reading from standard input, which would be somewhat confusing.

You could do this in many ways, but I'll show two variants of the enhanced script here:

#!/bin/sh -

if [ "$#" -eq 0 ]; then
    echo 'expected to get filename argument(s)' >&2
    exit 1

wc -l -- "$@"

This looks at the $# value, which is the number of command line arguments that the script got. If this is zero, the if statement will branch to the echo, which will output a diagnostic message and then terminate the script.

#!/bin/sh -

wc -l -- "${@?expected to get filename argument(s)}"

This does a similar thing but using a very different mechanics. The ${variable?word} expansion will result in the diagnostic message in word being written to the output if "$@" is empty or unset, and it will also terminate the script.

This may look like:

$ ./script.sh
./script.sh[3]: @: expected to get filename argument(s)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.