This homework is about calling a C program from a shell, and calling a shell command from C.
We have seen how to interact with the OS with calls that return a single value. However, there are many more things that we can do to interact with the system. Often, the results are more than a simple integer value. How do we get results that may be a large amount of text? This is the subject of this homework.
As you may remember, Unix has everything as a file.
You can use fopen
to open a file,
then fread, fwrite
or variations (fgetc, fputc, fgets
to name a few),
then fclose
to close it.
Similarly, you can use popen
to open a special process file,
then fread, fwrite
or variations (fgetc, fputc, fgets
to name a few),
then pclose
to close it.
The function getcwd
is like pwd but the "c" is for "current".
The getcwd
command has two arguments,
a buffer to hold the pathname, and the array's size.
Use MAXPATHLEN for buffer size.
This is defined in the include file
<libc.h>
or
<sys/param.h>
, depending
on the system.
The example below gets and prints the current working directory (getcwd).
char buffer[MAXPATHLEN];
getcwd(buffer, MAXPATHLEN);
printf(" Current working dir is %s\n", buffer);
You will need to include "unistd.h" for the getcwd
function.
The problem with getting results in text is that we (usually) do not know how much text there will be. To deal with this, we create a buffer then read the results into it. If the results are larger than the buffer size, then a second (or more) read will be necessary. The next example uses process I/O to show how we can invoke a system command, then read from the results.
// myProcess is a stream that we will read from.
// This is like reading input from the user, except that
// the input is coming from a different "file".
FILE *myProcess;
// buffer is a place to store text.
// Instead of reading from the stream 1 char at a time, we
// read many of them, defined as BUFFER_SIZE above.
char buffer[BUFFER_SIZE];
int count = 0;
printf("Here is a list of all files that end with .c\n");
// List all files in the current dir that end with .c
// Open the stream
myProcess = popen("ls *.c", "r"); // "r" for read
// Always check to see if the command worked.
if (myProcess == NULL) {
// It did not work, so quit.
printf("Error: popen command failed\n");
exit(1);
}
// Read up to BUFFER_SIZE-1 chars.
fgets(buffer, BUFFER_SIZE, myProcess);
// Check for end-of-file with the feof() function.
// It returns true when the file (i.e. myProcess in this example)
// has no more input to read.
while (!feof(myProcess)) {
count++;
// Print it out
// Notice that there is no \n since one is in the buffer
printf("File %d is %s", count, buffer);
// get the next line
fgets(buffer, BUFFER_SIZE, myProcess);
}
// Close the stream
pclose(myProcess);
You may wonder why we go through all of this trouble when we could simply
make a call such as the following.
system("ls *.c");The key difference is that our program does not know how many files match that pattern if we use "system". By using "popen/pclose", our program processes each line returned, and thus has control over what is displayed. It can count the number of files returned, and could look for a specific match among the results.
Using the asterisk as a wildcard in "*.c" is an example of a regular expression. If you want to know more about regular expressions, see page 665 in the Appendix in your Unix book.
When you use exit
, the number can be inspected by the shell
with echo $?
.
This can be put into code, with something like this:
if [ $? != 0 ]
then
echo " Error -- the last command failed"
exit 1
else
echo " The command worked"
fi
From the command line, use diff
on the two example .c files
(fopen_example.c and popen_example.c).
Describe the differences in your own words.
Are there more/fewer differences that you expected? Explain.
For your C program,
use the getcwd
command to get the current directory.
Print "Initial directory is " followed by what the current directory is.
Next, change the directory to "/usr/lib". It possibly could already
be that directory, but you can change to it without checking.
Use popen
to invoke the
du
command (disk usage),
examine the size and filename of each entry,
and print the top 3 (or fewer if there are
fewer than this).
You will need to get the size of the file followed by the filename
from each result read for the du
command.
Use sscanf
on strings (i.e. the buffer)
like you would with scanf
.
You will need to write a function to find the next space (char 32)
or horizontal tab (char 9) that
takes a pointer as an argument, and returns a pointer of the next space.
If the first character is a space/tab, then the returned pointer will be
the same as the argument. If there are no spaces/tabs, then the returned
pointer should point to the null character ('\0').
When you print the top 3, you will see that the first one always ends up being ".", the total amount for that directory. Get the amount of space that the other items, outside of the top 3, are taking by subtracting the second and third from the first. Output it as "The rest use" followed by the subtraction result.
Create a shell script.
The first thing that it should do is to use the date
command, which provides a time-stamp.
Then it should run your program.
If your program returns 0, indicate that it worked.
If it returns some other value, have the shell script print extra information
about what the problem is.
Also, the shell script should use cat
on the output file
that your program creates.
When you write your C function for the next space, how do you know that it works? You will need to do some tests: look for an easy case or two, and look for any "edge cases". An example of an easy case is the string "123 abc". Another one is "123\tabc", which has a tab after the 3. A couple of edge cases are "123abc" (i.e. there are no spaces), and " 123abc" (i.e. where a space is the first thing). To further check, print the string starting at the returned pointer and surround it with double quotes or some other character. This way, you can tell an empty string from one with only white-space characters. To test your code, have a "DEBUG" value defined. If it is true, try the examples out, and exit the program. If it is false, run the program normally. When creating a log file, you will need to show both possibilities.
Hints:
You will need to use the strncpy
command to copy one string
(i.e. file name) to another.
You will need to include "string.h" for that function.
Do not forget to turn in the shell script, too.