Hello, if you have any need, please feel free to consult us, this is my wechat: wx91due
P2T: C Programming under Linux
Lab 2: Advanced command line use and
branching and looping in C
Introduction
This lab will introduce some more advanced features of Bash. You will learn how to control running processes, and how to use the output of one process as the input to another. This will let you chain together simple commands in order to complete more complex tasks.
For the C component, we will introduce two mechanisms to write more expressive programs: loops (which let you repeat a set of actions more than once, until some condition occurs), and branches (which let you choose between multiple sets of potential actions, depending on a condition).
The assessed work in this lab consists of questions and programming activities which will be marked by your demonstrator. Each question states the number of marks available.
Getting Started
Start by opening theTerminal application and verify that you are in your home directory using thepwd command. As with lab 1, the files required for this lab can be obtained usingp2t-get-lab:
p2t-get-lab 2
Confirm you have the necessary files by listing the contents of this new directory:ls linux-lab02
Managing Processes
When developing scripts and programs, it is important that you know how to manage running processes properly. It is very easy to write a program which never terminates, for example by accidentally messing up a conditional statement and getting stuck in a never-ending loop. If this happens, you need to know how to stop the program forcibly.
Fortunately, it is relatively easy to manage processes in Linux. One tool which provides a useful overview of the activity on a Linux system istop, which performs a similar role to the Task Manager in Windows.
1. |
Runtop. The first few lines give a summary of the current system activity, including a breakdown of the load on the CPU. This is followed by a couple of lines describing the amount of used and available memory in the system. Finally, you will see a list of current processes. The information displayed will refresh every few seconds to reflect changes in activity. You can exittop by pressingq to quit. |
Questions These questions should be answered in the fileanswers.txt which can be found in thelinux-lab02 directory created at the start of this lab. |
||
1. |
Look at the information displayed bytop: |
|
a) |
What is the computer’s current load average? |
(1) |
b) |
How many processes (top calls them “tasks” here) are there on the system? |
(1) |
c) |
How would you show only the processes started by your user? Refer to the manual page if you are unsure. |
(1) |
|
|
(3) |
We are now going to explore the way processes behave within Linux. In particular, we’re going to examine the difference between foreground and background processes, and learn how to kill a badly behaved process.
2. |
In thelinux-lab02 directory, you will find a Bash script calledloop.sh. Before you can run this script, you will need to make it executable; if you are unsure how to do this, refer back to lab 1 or ask a demonstrator for help. |
3. |
Run theloop.sh script. It will repeatedly print the message “Hello World”. The quickest way to stop a misbehaving script such as this is usually to use theCTRL + C combination. Do this now to terminate the script and return to the command prompt. If you have left it running for a long time you may need to wait a while for the display to finish printing all the “Hello World”s that have queued up. |
Sometimes you may not want to terminate a program, but just pause it while you do something else. TheCTRL + Z combination lets you do this. You can then use thejobs,fg andbg commands to manage the operation of the program.
For this section, you will need to use a system that supports XForwarding (on a Windows system, we recommend MobaXterm to log in to brutha, as described in the Lab0 slides).
4. |
Start thexclock program by typingxclock -update 1. You can ignore warnings about fonts. Pausexclock by pressingCTRL + Z; the clock should stop ticking. (If it keeps ticking, make sure you have the terminal window selected and try again.) |
5. |
Use thejobs command to view a list of the currently running jobs along with their status. You should see an entry forxclock in this list. |
6. |
Start copies oftop andgedit, and pause them usingCTRL + Z. |
7. |
Run thejobs command again to see a list of the jobs which are currently running. You should see something like this: [gps@brutha1 ~]$ jobs [1] Stopped xclock -update 1 [2]- Stopped top [3]+ Stopped gedit |
8. |
The number inside the square brackets is used to identify the job. For example, to restarttop in the foreground, you would use thefg 2 command. Use thefg command to restartgedit. |
9. |
Insert some text into a document and then useCTRL + Z to pausegedit again (remember that you must have the Terminal window selected, and notgedit itself). What happens if you now try to insert more text? |
10. |
You can send a process to the background using thebg command, which allows it to run without interfering with the Bash prompt. Use thebg command to sendgedit to the background. jobs will now show that the process is running. Verify this by trying once again to add more text to the document. |
It is also possible to start a process in the background by including an ampersand (&) after the command:
|
gedit & |
This is useful if you wish to launch a graphical application from the command line but leave the terminal free to be used for other things. Just like when thebg command is used, any program launched in this way will be listed byjobs.
Another command you should be aware of isps, which displays a list of processes which are currently active. Theps command provides you with a PID, or Process ID, which can be used with thekill command to terminate that process. Note that the PID is not the same as the identifier provided byjobs.
11. |
Start a copy ofxclock in the background using&. |
12. |
Use theps command to check thatxclock is running, and note its PID. |
13. |
Use thekill command to terminate thexclock process. |
You can stop any program using justps andkill, but a few other related commands may help identify the relevant process more quickly. pidof lets you find the PID of a process by supplying its name, andkillall lets you kill all processes with a certain name. pkill lets you kill multiple processes, filtering on properties such as their name or owner. Read the manual pages for these commands to find out more about them. Theps command in particular has many options for filtering which processes it lists.
Note that if you try to kill a process which is currently stopped, it won’t actually be killed until you start it running again withfg.
Questions |
||
2. |
What effect does an ampersand (&) have when included after a command? |
(1) |
3. |
top andps both display information about running processes. How does the number of processes listed bytop compare to the number listed byps? Why do these numbers differ? |
(3) |
|
|
(4) |
Variables and the PATH Variable
Similarly to programming languages such as C, Bash lets you create variables which can be used to store values. However, unlike in C, Bash variables do not need to be defined; instead, they are created when you first set their value. Variables in Bash also do not have types; all Bash variables are considered to be strings.
Like C, variable names in Bash are case-sensitive. FOO andFoo are two different variables.
You can assign a value to a variable using the equals symbol, like this:
|
Lab="lab02" |
Unlike C, Bash isextremely fussy about spaces, so be careful not to include a space on either side of the equals sign!
To read a variable, you prefix its name with the dollar ($) symbol:$Lab. This is commonly used with theecho command, which prints out the value of a variable or expression:
|
echo $Lab |
We don’t tend to use our own variables on the command line very often, but they are used extensively when writing Bash scripts, which we will look at in the next lab. One set of variables which are used more frequently on the command line are called environment variables. As their name suggests, these variables are used by Bash to configure and store information about the current environment.
One example of an environment variable isPATH. You can print the current value of thePATH variable using the commandecho $PATH, and you can see a list of all your environment variables and their current values using theenv command. ThePATH variable itself contains a list of directories, separated by colons, which are searched when Bash is looking for a command to execute. You can run any command which is contained in one of the directories listed here from any other directory without needing to include its full path.
1. |
Use theecho command to print out the contents of yourPATH variable. |
2. |
Create a new directory in your home directory calledprograms. |
3. |
Add the new directory to yourPATH variable like this: PATH="${PATH}:${HOME}/programs" This setsPATH to its current value, but with the path to programs stuck on the end.(HOME is an environment variable which contains the path to our home directory.) |
4. |
Useecho to check that the new directory has been added to yourPATH. |
5. |
Copy the script calledhello from thelinux-lab02 directory into~/programs, and make sure that it is executable. |
6. |
Try changing to several different directories and runninghello. It should work from anywhere you try to run it. |
7. |
Close the terminal window and re-open it. Try running thehello command again. What happens? Look at yourPATH variable to discover why this is. |
As you will see, variables are not persistent. In other words, they only exist in the session in which they were created. If you want to make a variable persistent, you can set its value in your~/.bashrc file.
Questions |
||
4. |
What effect does adding a directory to yourPATH variable have? |
(1) |
5. |
Assume you have the following variables set: A=Apple B=Ball C=Cat What would the output be if you used theecho command to print the following? |
|
a) |
$A |
(1) |
b) |
$A$B |
(1) |
c) |
$AB |
(1) |
d) |
$Cat |
(1) |
e) |
${C}at |
(1) |
f) |
"$A $B $C" |
(1) |
g) |
'$A $B $C' |
(1) |
h) |
\$A |
(1) |
|
|
(9) |
C: Loops and User Input
Activity 1: The Newton-Raphson-Seki Method In Lab 1, we wrote code to improve guesses of the zeros of:
via the iterated process:
where However, because we didn't know how to write loops, we had to calculate each new guess separately. Now that we do know about loops in C, we're going to improve the code in several ways, starting by making it keep improving guesses until it gets close to an answer. Our "stopping condition" here is going to be when one of two things is true:
· the difference between
o Hint: this is the same as or · we have tried more than 20 times to improve our guess without getting close. (This is an important additional limit, as otherwise if the method failed somehow, we’d be stuck iterating forever – if we do hit this limit, we will know wedidn't find a root.) This is best represented with ado … while loop in C, as our main condition is “when this thing is true”. |
||
6. |
Make a copy of your code from Activity 2 calledcuberoots.cin the clabdirectory within linux-lab02, and edit it in the following ways:
We no longer needx1 orx2, because inside our loop we're going to update our guess directly. (We do need to store the "delta", Modify your calculations to calculatedxn and then updatex0 with the new value (as if it werex1). You may want to rename "x0" to "xn" here, since we're updating it with a new guess each time… |
(1) |
7. |
Write ado…while loop around the line you just wrote. You will need to add anint type variable to count the number of iterations you have done – because of the scope of a while loop, you need to declare thisoutside the loop. The condition for the while loop is for it tocontinue, so you need to test if the magnitude ofdxn is larger than1e-6 and we haven't made more than 20 attempts. Remember, the && operator combines two logical tests with a Boolean AND. |
(3) |
8. |
Test your code to see if it converges to the correct answer for a given value ofw. It would be more useful if we could ask the user for a value ofw to calculate the cube-root of when the code is run, to save us having to recompile the code each time. Add code before your loop to prompt the user for a number, and then read in a value to use for w. (You may usefgets +sscanf or justscanf here.) |
(3) |
9. |
Run your code and check it works for different values ofw. Add a comment to the code to evidence this. |
(1) |
10. |
Your code should be commented and laid out appropriately. |
(3) |
11. |
Your code should compile with no errors or warnings. |
(1) |
|
Your submission for this exercise is 1 piece of C source code. |
|
|
|
(14) |
Redirection and Pipes
Redirection
Bash allows you to redirect the output of a command to a file using the greater than (>) symbol. For example,ls -l > ls.txt will take the output ofls -l and place it in a file calledls.txt. This can be very useful if you want to save the output of a command for processing later.
Redirection is also useful when used with thecat command. When given a single file,cat prints out the contents of that file (e.g.cat data1.txt), but it takes its name from its ability to concatenate (or join) together multiple files. cat data1.txt data2.txt > data3.txt will combine the contents ofdata1.txt anddata2.txt, and save the result in a new file calleddata3.txt.
1. |
Change to thelinux-lab02 directory, and try using the above command to join the two data files together in a new file. |
Pipes
Pipes are similar to redirection, but they let you send (or pipe) the output from one command to another program instead of to a file. This lets you use the output of one program as the input to a different program. To pipe a command’s output, you use the pipe (|) symbol.
Pipes let you chain together multiple commands to perform more complicated operations. Let’s say you wanted to find the five most common values spread across several files. To do this, you would need to concatenate all the files together, sort them, count how many times each line occurs, sort the file again to find the highest totals, and finally use thetail command to get the last lines from the file. In other words, you would do something like this:
|
cat data1.txt data2.txt > all_data.txt sort all_data.txt > sorted_data.txt uniq -c sorted_data.txt > uniq_data.txt sort uniq_data.txt > sorted_uniq_data.txt tail -5 sorted_uniq_data.txt |
Alternatively, you could use pipes to join the commands together, allowing you to do this all in one line. This also removes the need to create files to store the intermediate results.
|
cat data1.txt data2.txt | sort | uniq -c | sort | tail -5 |
Questions |
||
12. |
In the above example using pipes, why is it necessary to sort the data files before using theuniq command? Read the manual pages foruniq if you are unsure. |
(1) |
13. |
Read the manual pages for thegrep command. |
|
a) |
In a single sentence, summarise what thegrep command does. |
(1) |
b) |
What command would you use to search for word “Chapter” in the ~/linux-lab02/extras/Bash-Beginners-Guide.txt file? |
(1) |
14. |
What does the following command do? Explain both the individual stages, and the effect of running it (i.e. what will the end result be)? sort ~/.bash_history | uniq -c | sort -n | tail > commands.txt |
(6) |
15. |
What is the difference between using> and>> for redirection? Try redirecting output to a file several times using each. |
(2) |
16. |
How would you count the number of processes usingps andwc? |
(2) |
|
|
(13) |
C: Loops and Conditionals
Note: “true” and “false” in C When we talk about conditional tests in C, things like2 == 1 to express the comparison “2 is equal to 1”, then we usually say that the result is eithertrue orfalse. (In this case, of course, it is false.) C doesn’t have a true concept of “true” and “false” as special values, however. Instead, it uses the value0 to represent “falseness”, andany other value at all to represent “trueness”. Built-in conditional tests like the one above will provide the value1 as their “true value”, but literally any other non-zero value will also be considered to mean “true” if you pass it to something that looks for true or false. For example, the if statement wants a true or false value to choose which statements to execute (it will execute the ones in the “if block” if it gets a true value), so: if (5) { puts("Hello!") } will printHello! to the terminal, as5 is definitely not a0, and thus is “true”.
The stdbool.h header To make things a little bit nicer, you can (as of C99 and later) use #include <stdbool.h> at the top of your source code. This gives you two special values,true andfalse, which are defined to be1 and0 respectively. It also gives you a “new” type –bool – which can only hold the values0 (false) or1 (true). bool my_result = (2 == 6); Note, again, this doesn’t change any of the above – thetrue thatstdbool.h gives you is still just the integer 1, and any other non-zero number will also be “true” for an if statement. In particular, true == 5 is stillfalse (sincetrue is really1 behind the scenes), even though: if (true) and if (5) have the same result. (true && 5 is still true though, as&& combines “trueness” not numerical value; and trying toassign the value5 to abool type variable will end up with it being set totrue.) The two values fromstdbool.h are really provided just to make your code more readable, anddon’t have any other special “true or false” powers.(This changes somewhat in C23, but most people are still using C17 or earlier.) |
Note: Complex types in C99 (and later)
As well as the “basic types” introduced in the previous lab, versions of C from C99 onward also provide some additional types which extend what we can do in the language. Some of these are “extended integers” mentioned in the side material on Moodle, but we’re going to introduce the other new types here, as they are very useful for physicists: the “complex floating-point” types.
(If you need a refresher on complex numbers, see the document in the Useful Information section of Moodle)
Using#include <complex.h> at the top of your source code allows you to use these extensions. Now, as well as writing:
float a_float = 5.0;
or
double a_more_precise_float = 5.0;
you can also declare float or double complex numbers:
complex double a_complex_double = 1.0 + 5.0*I;
whereI is a special value that represents the imaginary numberi.
You can multiply, add, subtract, and divide complex floating-point values just as you can scalar floating-point values, although if you write out complex literal values as part of an expression you will want to use parentheses to make clear the order of operations:
complex double result = (1.0 + 4.0*I) * a_complex_double;
Includingcomplex.h also gives you access to several useful functions, for example:
creal andcimag – which give the real and imaginary parts of a complex floating-point value as you’d expect (as a scalar value of the correct type), and
conj – which gives the complex conjugate of a complex floating-point value
So:
printf("The real part of 5.0 + 2.0i is %f\n", creal(5.0 + 2.0*I));
does what you would expect. (You can’t easily print pure complex values, however; you do need to break them into real and imaginary scalar values first.)
Those of you who have not done complex math in a while may find it useful to be reminded that the product of a complex number and its conjugate is equal to the square of its magnitude. We can use creal to then convert it back to a real double.
(Hence:
printf("The squared magnitude of z is %f\n", creal(z*conj(z)) );
}
This can be useful if you just care about how "big" a complex number is, and not what "direction" it points in.
Activity 2: Newton-Raphson-Seki Fractals
Whilst the Newton-Raphson-Seki method is a good approach to finding roots of equations, it has some quirks. Consider the case of the equation:
By inspection, its roots are at x = -1, 0 and 1. We might imagine that if we use the Newton-Raphson-Seki method to improve a guess until we find a root, we will always find the root our guess starts closest to, but this is not actually the case. If we write a loop to try different initial guesses for the root between -2 and 2, and then perform the Newton-Raphson-Seki algorithm for each initial guess, recording which root we find… …what we actually find is that, whilst most of the time it is true that we find the root closest to the initial guess, for initial guesses in a specific range between -1 and 0 we actually find the root x=1; and for initial guesses in a specific range between 0 and 1, we actually find the root x=-1 – in both cases, the rootfurthest from our initial guess! If we number the roots 1, 2 and 3 in order (so "x=-1" is "root #1"), and uses 0 for "we didn't converge", then we could print out a line of numbers to represent how the root we find changes as we move our initial guess, x0 from -2 to 2: 111111111111111111111111133332222222222222222222222211113333333333333333333333333 Here, the leftmost number is the root we get with an initial guess of -2, the rightmost number is the root we get with an initial guess of 2, and the values in between are the roots intermediate guesses converge to. You can see the anomalous regions, highlighted in bold, where we briefly converge to the "wrong" root. In this Activity, we're going to write code to investigate this same problem – starting with:
which has the cube-roots of unity ( As two of these roots are complex numbers, we will start with code that just looks for the real root. Rather than finding the solutions to the equation – which we already know here – we’re interested in seeingwhich solution we find, depending onwhere we start from in the complex plane. So, we want to test different values of x0(our initial guess) in a systematic way, and print out which root [we’ll number them 1, 2, 3 in order above] we reach. We’ll print 0 if we don’t find a root before we stop iterating. We want to try regularly-spaced values along the real line: · we’ll write a for-loop around the existing Newton-Raphson-Seki algorithm we wrote – to change pick different initial guesses · and use an if-elseif test to determine which root we get (and what we print out). |
||
17. |
Create a new filefor your program calledfractal.c bycopying your code forcuberoots.c to a new file. Remove the code for user input and simplify the rest of the code (as you no longer needw and can replace it with 1.0). |
(1) |
18. |
Afor loop is most commonly used to repeat the statements inside it for various different values of a “loop variable”. They’re better thando…while orwhile… loops for this, as all of the “loop variable” conditions are written together at the top of thefor loop, making them easy to spot by another human reading the code. For example with: for(int i=0; i<10; i++) { d += i; } it is clear that there is a “loop variable”, i, which changes every time around the loop – we can see that it is initialised to 0, it is incremented each loop, and that we continue looping as long asi is less than 10. Generally, we tend to use integer type variables as loop variables, mostly because floating-point arithmetic is slower than integer arithmetic, and is inherently inexact (which compounds errors with something like a loop). If you need a “floating-point” value that changes, you’ll often see something like: for(int i=0; i<10; i++) { double myvar = i / 10.0; …rest of loop here… wheremyvar is the actual value we care about, and we derive it from the loop variable directly. (This way, we also know we take exactly 10 samples, that are evenly spaced.) In our case, we want the "value" we calculate inside the loop to be our initial guess for x – and we want the loop to iterate over many samples within whatever range we want. (Inside the innermost loop, we want to do our Newton-Raphson-Seki algorithm we already wrote – the do-while loop from the original code – so we can find out which root we get) |
|
a) |
Write the for loop placing it around the algorithm we already have, and adding a line to assign the real part of You should change x0 between −2 and +2, but you may pick how many points you sample in this range. (for each initial guess, you run the full Newton-Raphson-Seki algorithm until you converge to a guess or fail to converge) |
(3) |
b) |
Add a suitable if/else test after the NRS step (butinside our for-loop) to test the result of the algorithm, and print out a number indicating the root we found. In this case, we only expect to: 1) fail to converge (if count is 20), in which case we should print 0 2) find the real root (if xn is > 0), in which case we should print 1 3) (otherwise, print 0 for now, as it's "not a root we expect”) |
(2) |
19. |
Compile and run your code.You should find, if you sample enough points in the range, that points almost always find the real root with for any real number guess except for some ranges of guesses where you fail to converge (most obviously, for guesses close to 0). This is interesting – but we mentioned before that there arethree solutions to our equation – two of which are complex values. |
|
a) |
Modify your code to perform its calculation using "complex double" type for xn and dxn. (You just need to change the type in the declaration). You can also modify your test for the loop to stop when the magnitude ofdxn is small (asdxn is now complex, you should usecreal(dxn*conj(dxn))to calculate the squared magnitude and compare to the square of our "small" value…), and test ifcreal(xn) is greater than 0 in your if/else test. At this point, try compiling and running your code again: you should get the same result, effectively. (Even though xn and dxn are now complex doubles, we aren't ever testing values for them which aren't real.) |
(2) |
b) |
Now, we're going to extend the range of values we test – as well as scanning over the real part of our guess, we also need to scan over the imaginary part. Add a second for-loop around the first for-loop - this one will pick a different imaginary component for our guess (so, for each imaginary component – or row - we will check all of the real components (or columns) we could combine with it to make a full complex number). You should also scan this imaginary component of x0 between -2 and 2, so we have a square block of values in the complex plane we sample from. Alter your assignment to xn's initial guess to add the imaginary component the outer loop generates. (for example, if the real and imaginary components your two loops make are called "r" and "i", you would write: xn = r + i*I; ) |
(2) |
c) |
We'll need toextend our if / elseif / else chain to print out 1, 2, 3 (or 0) depending on which root we found for a given Specifically: you should check · if the count is 20 first (because if it is, we didn't converge, and print 0) · then if the real part of xn is positive (if true, this is the 1st root) · Then a test to distinguish between the two complex roots (one has positive imaginary component, and one negative imaginary component). Also remember to print a newline at the end of each row. |
(2) |
20. |
The result of running your code now should be a square block of single digits (1, 2, 3 or 0) representing which root we converge to for each starting point in the complex plane. Each row is a set of guesses with the same imaginary component, each column a set of guesses with the same real component. This isn’t that readable – so we’re going to add a small amount of additional output to make this in to a “PGM format image” – a text format representing grayscale images with 1 number per pixel to give their brightness. |
|
a) |
Print out a “header line” before everything else, which should read:
For example, if your image was 40 × 40 values, the header would be: (you should, of course, use the number of values your for loops sample, instead of 40) Add a single line before the loop which prints out such a header line. |
(1) |
b) |
Add a space between each of our values. (This helps the PGM reader work out where one number ends and the next starts.) |
(1) |
21. |
By running your code andusing the Bash redirection operator to put its output into a file, make a file calledfractal.pgm containing an image representing your results. Check that this file represents an image by using (in a system with XForwarding enabled): display fractal.pgm to view it. You should see something like:
If the image is very small, try increasing the number of points you sample in the twofor loops… |
(1) |
22. |
Your code should be commented and laid out appropriately. |
(4) |
23. |
Your code should compile with no errors or warnings. |
(1) |
|
Your submission for this exercise is 1 piece of C source code and 1 PGM file. |
|
|
|
(17) |
Submitting Your Work
Your submission include:
· your answers.txt for the Linux parts
· the clab02containing 2 C source code files and 1 PGM file for the Activities
As with lab 1, you should submit this via
p2t-submit-lab 2
and submit the short code token you are given as your proof of submission to Moodle. As with Lab 1, you can use p2t-submit-lab as often as you want, but you should make sure that you submit the correct token for the submission you want to be marked.
Summary
Following this lab, you should know:
· How to usetop andps to view information about processes on the computer.
· How to kill a misbehaving program or script withCTRL + C.
· How to pause a process usingCTRL + Z, and how to manage active processes using thefg,bg andjobs commands.
· How to create variables and how to print their values usingecho.
· The purpose of thePATH environment variable.
· How to redirect the output of a command to a file, and how to pipe the output of one command into another.
· How to write C programs which repeat sections of themselves until a condition is met.
· How to write C programs which repeat a section a certain number of times.
· How to write C programs which perform different actions based on one or more conditions.
· How to read input from the users of a C program interactively.
Commands used in this Lab
Command |
Summary |
Description |
bg ID |
Background |
Resumes a suspended jobID in the background, as if it had been started with& |
cat FILE |
Concatenate |
Prints out the contents ofFILE |
cat FILE1 FILE2 |
Concatenate |
Prints out the contents ofFILE1 joined toFILE2 |
echo TEXT |
Print text |
Prints outTEXT, which may include variables |
env |
Environment |
Shows a list of environment variables and their values |
fg ID |
Foreground |
Resumes a suspended jobID in the foreground |
jobs |
Display jobs |
Shows information about active jobs |
kill PID |
Terminate a process |
Terminate the process with identifierPID |
ps |
List processes |
Shows a list of active processes |
sort FILE |
Sort |
Sorts the lines of text inFILE |
top |
Display Linux processes |
Shows information about processes and system resource usage |
uniq -c FILE |
Unique |
Filters matching adjacent lines fromFILE, and counts the number of occurrences of each unique line |