[Figures are not included in this sample chapter]

Sams Teach Yourself Linux Programming in 24 Hours

- 3 -

Writing a Linux Utility

Starting Project dos_cvrt

In this lesson, I will present the very basics of a Linux utility program using a primitive but simple command-line interface. This utility program will perform a conversion of DOS format text files into UNIX text format, and vice versa. You'll also look at how this utility can be invoked by different names and cause different actions to occur.

At the end of the lesson, you'll look at the labor-saving techniques of command-line editing. This knowledge will help you work through the remainder of the book in a more efficient manner.

The dos_cvrt utility that you'll write here will convert DOS text files into UNIX text files, and vice versa. Some UNIX hosts provide tools for this task, but these tools tend to be clumsy to use because they do not work as filters or work only with fixed numbers of arguments. Others may not have a tool at all. Before you reach the end of this book, you will have a full-featured DOS-to-UNIX text conversion utility tool. Here, you'll start with the basics, but as you progress through the book, you'll tweak and enhance the tool.

Planning for Your Project

You need a plan before you go running off to create C files. To keep things simple at the start, you also should keep the requirements simple. To this end, you'll begin by designing a program that decides what type of conversion it does based on the name of the program that it is invoked with. If the program is invoked with the name unix_cvrt, for example, the program expects the input data to be UNIX-formatted text and produce DOS-formatted text as output. You can also assume that if the program is invoked with the name dos_cvrt, or any other name, the input is in DOS text format and the program produces UNIX-formatted text as output.

The first command argument is the input filename to be opened for input, and the output is simply written to standard output.

Checking Out dos_cvrt.c for Editing

You started the shell of the program in Hour 2, "Managing Your Source Code." Listing 2.14 showed the present condition of the file dos_cvrt.c. Let's check out this file and apply some changes to it:

bash$ co -l dos_cvrt.c
RCS/dos_cvrt.c,v  -->  dos_cvrt.c
revision 1.3 (locked)

If you left a writable working copy of the work file dos_cvrt.c from Hour 2, then you might get a session that looks like this:

bash$ co -l dos_cvrt.c
RCS/dos_cvrt.c,v  -->  dos_cvrt.c
revision 1.3 (locked)
writable dos_cvrt.c exists; remove it? [ny](n): y

If the file should be removed, simply answer by typing "y" to indicate that it can be removed and replaced with the actual file that will be checked out.

Selecting the include Files

Usually, when you begin writing a C program, you try to anticipate what include files you need. Here, you will be using the standard C I/O functions, so you know that you need the stdio.h include file. Unless you have a good reason to omit these files, you should also include files stdlib.h and unistd.h when programming for UNIX. The stdlib.h file includes definitions of the common C language library functions, such as the exit() function. The include file unistd.h defines many commonly used UNIX functions, such as read(), write(), and close(). The unistd.h file also defines the external environment variable environ, which you'll learn about in Hour 8, "The Linux main Program and Its Environment."

All three of these files are already included in the shell of the C program that you have in file dos_cvrt.c, from Hour 2. As it turns out, you also need the help of a string function, so you can add the include file for string.h as well.

Writing the dos_cvrt Utility with Stubs

A good place to begin is to determine how the command is being invoked. Remember that you have a requirement for the command to behave differently, depending on the name of the command as it is invoked.

To code for this requirement and to allow you test it, you can code your utility with stubs in place of the actual code that will be provided later. A "program stub" is a statement or function that substitutes for the real code.

Listing 3.1 shows how to use stubs.


1:   /* dos_cvrt.c */
3:   static const char rcsid[] =
4:       "$Id: dos_cvrt.c,v 1.3 1998/11/21 22:49:39 student1 Exp student1 $";
7:    * $Log: dos_cvrt.c,v $
8:    * Revision 1.3  1998/11/21 22:49:39  student1
9:    * Demonstration of RCS substitutions
10:   */
11:  #include <stdio.h>
12:  #include <stdlib.h>
13:  #include <unistd.h>
14:  #include <string.h>
16:  int
17:  main(int argc,char **argv) {
18:      char *base_name = 0;    /* Basename of command */
20:      /*
21:       * Determine the basename of the command name. This
22:       * is necessary since this command could be invoked
23:       * with a pathname. For example, argv[0] could be
24:       * "/usr/local/bin/unix_cvrt" if it was installed
25:       * in the system that way.
26:       */
27:      if ( ( base_name = strrchr(argv[0],'/') ) != 0 )
28:          ++base_name;        /* Skip over `/' */
29:      else
30:          base_name = argv[0];/* No dir. component */
32:      /*
33:       * Now that we know the basename of the command
34:       * name used, we can determine which function we
35:       * must carry out here.
36:       */
37:      if ( !strcmp(base_name,"unix_cvrt") ) {
38:          /* Perform a UNIX -> DOS text conversion */
39:          puts("UNIX to DOS conversion");
40:      } else {
41:          /* Perform a DOS -> UNIX text conversion */
42:          puts("DOS to UNIX conversion");
43:      }
45:      return 0;
46:  }

Listing 3.1 shows the result of the following editing steps:


1. The string constant rcsid[] is added (line 3).
2. A revision history is added in lines 6 to 10. RCS provides the information in lines 7 to 9.
3. The "puts("dos_cvrt.c")" call is removed from the dos_cvrt.c (revision 1.2) used in Hour 2.
4. Lines 18 to 44 are added to fill in the rest of the new utility.


NOTE: The changes to dos_cvrt.c shown in Listing 3.1 have not been checked into RCS yet. You still have the writable dos_cvrt.c work file. When you actually check in these changes, this source file will become revision 1.4.

The major chunk of code added to the main() function requires some explanation:


1. The main program begins by extracting the basename of the command name in lines 27 to 30 of Listing 3.1. The C function strrchr() is used to determine where the last slash is located.
2. If no slash is located in the pathname, strrchr() returns a null pointer. It tells you that variable argv[0] contains the basename already.
3. If, however, strrchr() does return a pointer, you increment past the slash character in line 28. This causes the variable base_name to point to the basename within the pathname.
4. Line 37 tests to see whether the basename matches the string "unix_cvrt". If so, then you know that you are doing the UNIX-to-DOS text conversion according to your program specifications.
5. Otherwise, in line 40, you assume that if you don't have a name match in line 37, the required conversion is DOS text format to UNIX format.


NOTE: A basename is the filename component of a pathname. It is the file's name without any directory component attached. For example, the pathname /usr/local/bin/dos_cvrt can be broken into the directory component /usr/local/bin and the basename component dos_cvrt.

In lines 39 and 42, notice that you simply put calls to puts() there to tell you what is happening. These calls are program "stubs" that will be filled in with real code after preliminary testing.

TIP: Using stubs in programs under development can help you debug a program. Testing with stubs can often point out early design problems.

Testing the Utility with Stubs in Place

With your stubs in place, you are now ready to test the improvements to this program. Before you do that, however, check in the changes made in Listing 3.1. After you do so, that edit becomes known to RCS as revision 1.4.

By using the ci command with the -l option, you can perform a checkpoint release:

bash$ ci -l dos_cvrt.c
RCS/dos_cvrt.c,v  <--  dos_cvrt.c
new revision: 1.4; previous revision: 1.3
enter log message, terminated with single `.' or end of file:
>> Coded basename test
bash$ gcc -Wall -D_GNU_SOURCE dos_cvrt.c -o dos_cvrt

This operation accomplishes two things at once:


1. It checks in the changes you made (shown in Listing 3.1). They are registered as revision 1.4.
2. It checks out and locks a new work file dos_cvrt.c, which will later become _revision 1.5.


Now it is time to test the program's functionality. You can easily test the DOS-to-UNIX conversion process by simply invoking the program:

bash$ ./dos_cvrt dos_file
DOS to UNIX conversion

Note that the output shows the intention to do the "DOS to UNIX conversion." To test the other conversion, you need to invoke this command with another name--namely unix_cvrt. You can easily accomplish this feat under Linux by linking this second name to the first:


1. Link the dos_cvrt executable to the name unix_cvrt using the ln command.
2. Invoke the link name unix_cvrt instead of dos_cvrt.


The following is an example of what that session might look like:

bash$ ln ./dos_cvrt ./unix_cvrt
bash$ ./unix_cvrt unix_file
UNIX to DOS conversion

You see that this operation indeed does work. Note that the command-line argument unix_file does not actually exist presently as a file, but the command pays no attention to this fact.

Replacing the Stubs

Now it's time to fill in the rest of the program and replace the stubs. This time, assume that you've edited the program dos_cvrt.c to replace the stubs with the actual conversion code that you need. Now you can check in the finalized code by using the ci command and the -u option:

bash$ ci -u dos_cvrt.c
RCS/dos_cvrt.c,v  <--  dos_cvrt.c
new revision: 1.5; previous revision: 1.4
enter log message, terminated with single `.' or end of file:
>> Completed utility & tested ok.

The check-in procedure here accomplishes the following two things in one command:


1. It checks in the current edit. This process registers the changes made to replace the program stubs.
2. The -u option causes ci to leave behind a read-only copy of the source file dos_cvrt.c. This copy of revision 1.4 now includes the registered changes to the stubs.


Listing 3.2 shows what the final source code looks like. This time, these changes have been checked in, so the comments in line 8 reflect the true version of the file that you are looking at.


1:   /* dos_cvrt.c */
3:   static const char rcsid[] =
4:       "$Id: dos_cvrt.c,v 1.5 1998/11/23 05:32:21 student1 Exp $";
7:    * $Log: dos_cvrt.c,v $
8:    * Revision 1.5  1998/11/23 05:32:21  student1
9:    * Completed utility & tested ok.
10:   *
11:   * Revision 1.4  1998/11/23 05:04:23  student1
12:   * Coded basename test
13:   *
14:   * Revision 1.3  1998/11/21 22:49:39  student1
15:   * Demonstration of RCS substitutions
16:   */
17:  #include <stdio.h>
18:  #include <stdlib.h>
19:  #include <unistd.h>
20:  #include <string.h>
22:  int
23:  main(int argc,char **argv) {
24:      char *base_name = 0;    /* Basename of command */
25:      int ch;                 /* Current input character */
26:      int cr_flag;            /* True when CR prev. encountered */
27:      FILE *in = 0;           /* Input file */
29:      /*
30:       * Determine the basename of the command name. This
31:       * is necessary since this command could be invoked
32:       * with a pathname. For example, argv[0] could be
33:       * "/usr/local/bin/unix_cvrt" if it was installed
34:       * in the system that way.
35:       */
36:      if ( ( base_name = strrchr(argv[0],'/') ) != 0 )
37:          ++base_name;        /* Skip over `/' */
38:      else
39:          base_name = argv[0];/* No dir. component */
41:      /*
42:       * Open the input file:
43:       */
44:      if ( argc != 2 ) {
45:          fprintf(stderr,"Missing input file.\n");
46:          return 1;
47:      }
48:      if ( !(in = fopen(argv[1],"r")) ) {
49:          fprintf(stderr,"Cannot open input file.\n");
50:          return 2;
51:      }
53:      /*
54:       * Now that we know the basename of the command
55:       * name used, we can determine which function we
56:       * must carry out here.
57:       */
58:      if ( !strcmp(base_name,"unix_cvrt") ) {
59:          /* Perform a UNIX -> DOS text conversion */
60:          while ( (ch = fgetc(in)) != EOF ) {
61:              if ( ch == `\n' )
62:                  putchar(`\r');
63:              putchar(ch);
64:          }
65:      } else {
66:          /* Perform a DOS -> UNIX text conversion */
67:          cr_flag = 0;    /* No CR encountered yet */
68:          while ( (ch = fgetc(in)) != EOF ) {
69:              if ( cr_flag && ch != `\n' ) {
70:                  /* This CR did not precede LF */
71:                  putchar(`\r');
72:              }
73:              if ( !(cr_flag = ch == `\r') )
74:                  putchar(ch);
75:          }
76:      }
78:      fclose(in);
80:      return 0;
81:  }

The following changes have been made to the program in Listing 3.2:


1. To support the changes, variables are added in lines 25 to 27. Variable ch is simply the current character read. The FILE variable in is a pointer to the opened input file. The DOS-to-UNIX text conversion code uses variable cr_flag.
2. Lines 44 to 47 must make sure that the command line includes the filename argument. Note that you test for a count of two arguments because the command name in argv[0] counts as one.
3. In lines 48 to 51, you have code installed to open the input file or exit with a status 2 if unsuccessful. The error message in line 49 is not too friendly to the user here, but you'll remedy that problem later.
4. The UNIX-to-DOS conversion code is shown in lines 60 to 64. The program simply reads one character at a time into variable ch until it encounters "end of file." Within the loop at line 61, the program tests whether the character is a newline (linefeed) character. If it is, the DOS return character is put to standard output before the newline is.


5. The DOS-to-UNIX conversion code is added in lines 67 to 75.

Performing the DOS-to-UNIX Conversion

The DOS-to-UNIX conversion code in lines 67 to 75 of Listing 3.2 requires a bit of explanation. You might be tempted to simply assume that you can strip all DOS return characters out of the input stream and allow the normal newline characters to go out on their own. In practice, this method may work most of the time. The problem with this approach, however, is that sometimes a return character may be embedded in a DOS text line before the end of the line. This type of coding is often done for overtyping on impact printers for extra boldness or overstriking. Allowing those characters to go through as is requires a slightly different approach, as you can see here:


1. Line 67 initializes cr_flag to false. This flag is set to true whenever a return character is encountered.
2. A character is read (line 68) until the end of file is reached.
3. If the cr_flag is not true, then proceed to step 5.
4. If the cr_flag is true, and the character read in step 2 is not a newline (linefeed) character, then you emit a return character. You do so because you did not get a return and newline pair.
5. Line 73 notes whether you got a return character by setting cr_flag. If cr_flag is set to false, indicating that you did not get a return character, then you immediately emit that character because it requires no further processing.
6. At the bottom of the loop in line 75, you return back to step 2.


Testing the Utility

Now you can try this utility. You might not have a DOS text file readily available, but this is no problem because this wonderful utility can convert both ways. The procedure you'll use is as follows:


1. Compile the new utility.
2. Remove the existing unix_cvrt executable link, if it still exists. If it exists, it references an old executable.
3. Link the newly created dos_cvrt executable to the name unix_cvrt.
4. Using the new unix_cvrt utility, convert the UNIX-formatted text in dos_cvrt.c to a file named dos.txt. This new file will be in DOS text format.
5. Using the cat command with the -v option, verify that the new file dos.txt is indeed in DOS format.
6. Provided that step 5 succeeds, convert the DOS text file dos.txt back into a UNIX text file using the dos_cvrt utility you just compiled. The file created will be named unix.txt.
7. Using the cat command with the -v option again, display the tail end of the unix.txt file to see whether it has been converted successfully back to UNIX format.


Listing 3.3 shows a sample session using the procedure just outlined.


1:   bash$ gcc -Wall -D_GNU_SOURCE dos_cvrt.c -o dos_cvrt
2:   bash$ rm unix_cvrt
3:   bash$ ln dos_cvrt unix_cvrt
4:   bash$ ./unix_cvrt dos_cvrt.c >dos.txt
5:   bash$ cat -v dos.txt | tail
6:               }^M
7:               if ( !(cr_flag = ch == `\r') )^M
8:                   putchar(ch);^M
9:           }^M
10:      }^M
11:  ^M
12:      fclose(in);^M
13:  ^M
14:      return 0;^M
15:  }^M
16:  bash$ ./dos_cvrt dos.txt >unix.txt
17:  bash$ cat -v unix.txt | tail
18:              }
19:              if ( !(cr_flag = ch == `\r') )
20:                  putchar(ch);
21:          }
22:      }
24:      fclose(in);
26:      return 0;
27:  }
28:  bash$

CAUTION: Be careful when you're working with linked files. After a recompile, your link may be stale. Always remove it and re-establish the link afterward to be sure.

TIP: To test whether two particular filenames are linked to the same physical file, you can use the -i option in the Linux ls command to list the i-node numbers. If these files are on the same file system, and the i-node numbers match, then they are linked to the same file.

Step 6 of the procedure allows for a visual inspection of the final text file unix.txt. You know, however, that the resulting file unix.txt should be identical to the file you started with in step 4. Using the Linux diff command as follows, you should be able to prove that no differences have crept in:

bash$ diff dos_cvrt.c unix.txt

Apparently, there are no differences. The utility is a success. Don't get too hasty in your celebration, however, because there is still plenty of room for improvement.

Reviewing the Project

Even though the conversion utility gets the job done, looking at the program now is useful to see how you can improve on it. You might be tempted to immediately start to enhance the program without paying attention to the other important details such as improving its error handling and its messages. However, a developer should not stop working on a program simply because it seems to work. The program may have usability problems and reliability problems that you've not yet encountered in your testing. Perhaps you've already noted some of the weaknesses in your program.

One problem with the program at present is that it is not modular. All the code is contained in one source module, and even worse, all the code is contained within the main() program. If this was the full extent of the program, leaving it this way might be acceptable. However, you already know that you plan to enhance this utility, so this structure will not do.

If this program were larger, this structure would not be the best for longer term maintenance. Small changes become more difficult because you must adjust a lot more code. Small changes also invoke large compiles instead of small ones. You'll look at the modularity issues in Hour 4, "Modular Programming in C."

Another weakness in this program is the error message that the user gets if the program is unable to open the input file. Look at the following command session:

bash$ ./dos_cvrt oink >out.txt
Cannot open input file.

Here, you get a terse message saying that the program cannot open the input file. "Well, thank you very much," you might be inclined to say, as a user of this utility, "but why can't it open that file?" The reasons may vary from the file not existing to a lack of permissions to open it.

Not only is the reason for the open failure missing, but also, the identity of the file that failed to open is missing. This information will become important when you enhance the utility to allow for more than one input file. Even as it stands, however, if this program were invoked from a shell script with a number of other commands, the message identifying both the reason for failure and the filename would be much better for troubleshooting.

More user unfriendliness comes from the message when you don't specify an input file:

bash$ ./dos_cvrt
Missing input file.

Notice that the message tersely says "Missing input file". "Well, thank you very much, but how do I use this thing?" might be the grumble of your users. The users don't know whether they should use shell input redirection or provide the filename on the command line. A message showing how the command should be used would be helpful, or at least a short message telling the users how to obtain such documentation from the command is needed.

To support additional enhancements to this utility, you need command-line option processing. You'll look at this topic in Hour 6, "Linux Command-Line Option Processing."

Before you leave this program review, note that one more weakness to this program has not been looked at. You have avoided any form of error checking when you actually do the conversion on the text files. You do not check for read errors when reading, and you don't check that all our output gets written. A common problem is to run out of disk space. Yet your program glibly assumes upon completion that all is well and returns a status code of zero to the shell. If a shell script checks this code, it then also assumes that the text conversion was a success, which could be dangerous. You'll look at error reporting in Hour 7, "Error Handling and Reporting."

Using Command-Line Editing

If you have been working through the examples I've provided, you might have found that typing in the long command lines for each compile can be a bit of a nuisance. In fact, you may even have a shell alias or shell script defined by now to make your job easier. In any case, take a quick look at the Linux bash shell facilities for editing and recalling command lines. You may be unaware of this capability, which might save you a lot of effort.

The GNU bash was created by the Free Software Foundation and named from the acronym for "Bourne-Again SHell". The Bourne shell is the standard shell on most UNIX systems, whereas bash is an advanced version of this shell. Its functionality actually borrows heavily from the newer UNIX shell known as the Korn shell.

Selecting a Command-Line Editing Mode

One of the new features of the UNIX shell is the capability to provide command-line editing (both bash and Korn). When you catch on to this feature, you'll be amazed at the bare editing facilities of the NT DOS prompt.

The default for the bash shell is to use the emacs-styled editing mode. You can also activate this shell at any time you are in another mode by typing the following:

bash$ set -o emacs

When you're in this mode, you have a subset of the emacs commands at your disposal. For example, if you want to retrieve the last command that you typed, you can press Ctrl+P and then press Enter (Ctrl+P is an emacs way of moving to the previous line).

Many users, of course, like the vi editor's way of doing things. Bash caters to this whim by allowing you to select vi mode, as follows:

bash$ set -o vi

Now that you have this mode set, you can use vi keystrokes to get things done. Note, however, one small difference: In vi, you are normally in command mode, and you use "i" or some other command to enter an "input mode". For command lines, you are in input mode by default and must press the Escape key to enter a command mode. For example, to back up to the previous command, you press Escape to enter command mode and then press the "k" key ("k" in vi moves up to the previous line). To append to the current line, you might enter the "A" and then start typing. The command is initiated when you press Enter. You might find that using vi mode takes a bit of practice.

Performing Command-Line Searches

Bash also can conveniently search back through your command history.

Using emacs Mode Searches

In emacs mode, you can perform the following:


1. Press Ctrl+R (emacs reverse incremental search).
2. Type in a fragment of the command that you are looking for.
3. If the first search is not what you require, press Ctrl+R again to repeat the search.
4. When the required result has been located, press Enter to execute that command. Otherwise, apply other emacs commands to edit the result before executing it.


Next, you can set emacs mode and perform a search. Press Ctrl+R, and immediately bash prompts you with "(reverse-i-search)" (this means reverse incremental search). Then you type in vi to search back to the prior vi setting command, and immediately the search yields the text "set -o vi" to the right of where you're typing:

bash$ set -o emacs
(reverse-i-search)'vi': set -o vi


1. You set emacs mode (just in case you have vi mode set). Then you press Ctrl+R to start a reverse incremental search.
2. You then type the fragment "vi".
3. The bash shell immediately shows you the command "set -o vi" as a result.
4. To use this result as is, press Enter. However, you could start editing that command if you enter some other emacs editing commands.


Using vi Mode Searches

In vi mode, you follow a slightly different procedure:


1. Press the Escape key to enter "vi command mode". By default, bash allows you to "enter data".
2. Type in one (forward) slash. For Microsoft types, this is the "/" character. This slash indicates a backward search through your command history.
3. Enter a command fragment to base your search on. In the example shown next, you'll use the fragment "o emacs".
4. Press Enter to start the search.
5. The search result is displayed on the command line. To execute it, press Enter. Otherwise, you can apply other vi commands to edit this command.
6. To repeat the search because the incorrect entry was shown, type a single slash character again and go to step 4.


An example of how this procedure might look is shown here:

bash$ /o emacs

After you press Enter to activate the search, the command line shows the prior "set -o emacs" command from the command history as the current line, as you can see here:

bash$ set -o emacs

Using Forward vi Mode Searches

In vi mode, the slash searches backward through your command history, and the question mark searches forward. This use may seem a bit counterintuitive because normal vi editor behavior is the opposite of this in a file. However, it just works this way (presumably because the slash is the easier thing to type).

Listing Your Command History

The bash shell has another trick up its sleeve: You can list your recent command-line history. This capability can be useful when you want to see what has been done recently. This history can also be useful when you are having difficulty finding the command you want. Finally, this history display is necessary if you want to recall a command using command recall by number. Consider the following:

bash$ fc -l
485      clear
486      set -o vi
487      fc -l
488      clear
489      mount
490      man bash
491      set -o emacs
492      ls -l /usr/local/bin
493      echo $PATH
494      emacs ~/.profile
495      ps -ax
496      pine
497      lpstat -t
498      clear
499      ls -l
500      pwd
501      sync

The fc command is a built-in command of the bash shell program. It lists all recently used commands. You can recall any one of these commands by number:

bash$ !500

You simply precede the number with an exclamation mark, and the command that gets executed is shown on the following line.

This lesson provided a very brief introduction to some of the most essential aspects of command-line editing. I've included this information to help equip you for more work tofollow.


In this lesson, you did some project planning and started writing the utility program with stubs. You later replaced those stubs with the actual code that performed the conversion. You also practiced using RCS, as you built up the program. You tested the program by converting text to DOS format and then converted it back to UNIX format. You concluded that section with a look at the present limitations of the project.

In the remainder of the lesson, you learned about the command-line editing features that are available in the bash shell. Mastering these features will make you much more efficient as a developer.



Q The utility program dos_cvrt.c does two different text file conversions. How does it determine which conversion to perform?
A The program looks at the command name provided to the main() program in argv[0]. The same executable file can be invoked by different links to it. Based on which link name is used, the program can make a choice.
Q Why is the basename of the executable filename used for comparison? Why not just use the argv[0] value as it is supplied?
A Using the basename is the only reliable way to test for the command name because the same program can be invoked from multiple directories and in multiple ways in the current directory. For example, the command can be invoked using the dot slash method, as well as just by using the bare command name. In short, using the basename eliminates the need to test for all the possibilities.
Q What is a stub in a program?
A A stub is a simple piece of programming that substitutes for the real thing. Often it is just a print statement.
Q Why is it necessary to remove the alternate link of unix_cvrt after each compile of dos_cvrt?
A Depending on the current behavior of the gcc command, it might remove the link to dos_cvrt before writing out the new dos_cvrt executable. If this happens, the old link unix_cvrt still points to the old executable program. For this reason, you should always remove the alternate link (unix_cvrt) and then link the new executable with the alternate link name.



To practice what you have learned in this lesson, you are encouraged to answer the quiz and work through the exercises.



1. What is the value of argc if the program is provided two command-line arguments?
2. What is the value of argc if the program is provided one option and two command-line arguments?
3. Given two links to a file on the same file system, how do you prove that they point to the same physical file without changing the file's contents?
4. Can a link exist to your executable from two different file systems? Why or why not?
5. What are two reasons why a file might not be successfully opened for input?
6. Why might an output operation to standard output fail?
7. Is bash part of Linux, or is it a program that runs on top of the Linux kernel?
8. How do you set vi command-line editing mode?
9. How do you display recent command-line history?




1. Copy the program 03LST02.c to a new work area for your experiments. Modify the program to accept more than one input file.
2. Modify the finished program from exercise 1 so that it works with zero input files if none are given--that is, to accept input from standard input. When a program operates this way, what is it called?