Department of Mathematics
and Computer Science
San Jose State University
Review of DOS
In Lab 1 it was noted that, while many disk and file management commands can be carried out from a graphical user interface such as Microsoft Windows, using DOS can be faster, particularly for repetitive tasks. In this lab you will learn how to automate DOS by creating batch files consisting of sequences of DOS commands that can be executed again and again (indeed, writing batch files is a kind of programming). First, however, this lab will help you to review what you learned in Lab 1 about DOS.
For this lab, you need two formatted empty floppy disks. Format
the two disks now if they are not already formatted.
For this lab, you need a directory j:\cs46a\lab\lab6, and inside this directory a directory work. Make these subdirectories now from the DOS prompt, and then copy all the files from k:\cs46alab\lab6 to j:\cs46a\lab\lab6.
Change the current directory to k:\cs46alab and issue
a dir /s /p command.
Now issue the command
dir /s pastri.cpp
Now issue the commands
j:
cd \cs46a\lab\lab6
xcopy /s k:\cs46alab\*.* work
copy k:\cs46alab\*.* work
Make a directory named to in your current directory, copy the file k:\cs46alab\lab6\hello.cpp to work, and then move it to to.
Now rename the directory
to as target, and then delete the directory target.
Batch Files
Every day you log onto the computer, you do many of the same things:
1. Run doskey/insert (just to make sure it's installed, and with the right option).
2. Copy any files that you have worked on at home from a: to j:.
3. Change to the j:\cs46a\lab directory (or to another similar directory).
Rather than typing the commands every time, you can place them into a batch file, and run the batch file as a single command under DOS. We will start with a simple version of such a batch file.
Start Notepad from the Programs | Accessories menu. Type in the following text:
@echo off
rem Startup batch file
doskey /insert
cd work
rem Copy all files for now
copy a:*.*
Then use the File | Save as menu option to save the file as j:\cs46a\lab\lab6\start.txt. (Notepad will automatically give it the txt extension, so you should just type start in the filename field of the dialog box. Now exit Notepad and change the name of the file (using Explorer or the DOS move command) to start.bat (all batch files must have the bat file extension).
You can use also the editor of the MSDS C++ compiler to edit any text files, not just code files, but Notepad is easy to use too. One advantage to using MSDS is that you can save the file directly as a .bat file, without changing the name.
Now switch back to the DOS Command Prompt, make sure the current directory is still j:\cs46a\lab\lab6, put a disk in drive a:, and type
start [Enter]
You execute the commands
in a batch file by typing the name of the file (without the extension .bat).
The first two commands of the start batch file are new commands. @echo off suppresses the display of each command on the screen as it is executed. A line starting with rem contains comments that are ignored by DOS.
When you are done for the
day, you should back up all your work. Of course, you could simply do
copy *.* a:
but then you copy all the .obj and .bak files that you don't really need. Copying takes time and causes wear and tear on the floppy. Most importantly, your disk may fill up with junk files, leaving you with insufficient space for the files you need. Really, you mostly want to copy all files with extension .cpp, .h, .txt and .bat.
Batch File Arguments
Suppose you sometimes want to save your work to a:, other times possibly to b:, and maybe you even want to use the same batch file to copy to j: from a: or k: or even to c: at home. This can be easily done by modifying save.bat.
Change the lines
copy ... a:
to
copy ... %1:
(Be sure to place the colon after the %1)
Run save a
Note that %1 has been replaced by a. %1 is the first argument of the batch file, %2 is the second, and so on.
Let's modify the batch file
to give an error message if no command line argument is specified.
Add the following lines:
if "%1" == "" goto error
(keep the copy commands here)
goto end
:error
echo Usage: save drive
echo Example: save a
:end
The if "a"
== "b" command tests whether the strings a and
b are equal. The goto command goes to a label. Each label
starts with a colon (:). The echo command displays a message
on the screen.
Let us add more error checking.
We want to see that the argument is an existing drive. Add the following
lines after the if "%1" == "" goto error
for %%f in (A,a,B,b,C,c,J,j) do if "%1" == "%%f" goto ok
goto error
:ok
Redirection
Suppose you want to print a directory listing. How can you do it? Dir shows the display of the directory on the screen. If it happens to fit on one screen, you can use the Edit option of the shortcut menu of the DOS window to copy any part of the screen to the Windows Clipboard, and then paste it into Notepad. There is a better way.
Type the following commands
k:
cd \cs46alab\lab6
dir /S > j:\cs46a\lab\lab6\out.txt
j:
cd \cs46a\lab\lab6
Then view the file out.txt:
notepad out.txt
The redirection > out.txt sent all output to a file instead of the screen. Now you can look at the file, edit it, or print it with Notepad or the MSDS editor.
The tree command outputs
DOS graphics characters to the screen, which are not recognized in Windows
applications. Thus, if you look the file kdirs.txt in Notepad, it
will not be correct. In the MSDS editor, however, it will look correct.
You can also supply the input to programs in a file rather than keying it in. Consider the following example.
del work
You must enter a Y
if you want del to go ahead. When the del command is contained
in a batch file, it will be irritating if the user has to enter Y
at just the right time. But we can store the input in a file.
Prepare a file y.txt that contains just one line with just the letter Y followed by [Enter]. Then type
del work < y.txt
Now del reads the Y keystroke from the file y.txt. You can place this line in a batch file, and the batch file will run silently without bothering the user.
Some commands are specifically designed to work with input from the keyboard (thus allowing redirected input). The sort program is an example.
Try the following:
sort < out.txt
The sort program reads
from standard input (that is, the keyboard or a redirected file). It is
possible (although not very useful) to have sort sort some lines
of keyboard input.
Type sort and then the following lines. Hit [Enter] after each line. The [Ctrl-Z] command tells DOS to end keyboard input.
This
is
a
test
[Ctrl+z]
Try sort without the
<
sort out.txt
To sort a directory, you
can first type dir > out.txt and then sort < out.txt.
Because this combination is so frequent, there is a shortcut for it, called
a pipe.
Type in the following:
dir | sort
This pipe, written with a vertical bar, runs dir, stores its output in a temporary file, then runs sort and redirects its input from that temporary file, and then automatically deletes the temporary file.
Another useful program that reads from standard input is more. More stops every screen full and waits for you to hit a key before continuing.
Type in
dir /S | more
You can pipe any sequence of commands together.
Type in
dir /S | sort | more
echo Y | del a:*.*
or
del a:*.* < y.txt
Why?
Writing your own Filters
A program that reads from standard input and writes to standard output is often called a filter. More and sort are examples of filters. In this exercise, we will write our own filter that adds line numbers to a file.
Compile the program linenum.cpp (or use the supplied .exe file).
Run
linenum < hello.cpp
To create a file with tabs
expanded to spaces and with line numbers, use (the tab program is
from Lab 2):
tab < hello.cpp | linenum > hello.txt
Make a batch file pprint.bat that creates a "pretty print" version of its first argument in a file whose name is given by its second argument. The "pretty print" version should have tabs expanded to spaces and line numbers. For example,
pprint hello.cpp hello.txt
should have the same effect as the previous command.
Error Level
Look at the hello.cpp program:
int main()
{
cout << "Hello,
World!\n";
return EXIT_SUCCESS;
}
The main() function returns EXIT_SUCCESS, although it is not clear how anyone could test its return value. You may have heard that the convention is to return 0 when a program is successful and a non-zero value to indicate some kind of error. The debugger actually shows the return value of the program. More importantly, you can test the return value of a program in a batch file.
Consider the program fcomp.cpp with the following main() function. The program takes two file names on the command line, returns 0 if two files are identical, 1 if the files differ, and 2 if one or both files don't exist.
int main(int argc, char* argv[])
{ fstream f1; /* first file
to compare */
fstream f2; /*
second file to compare */
string ch1; /*
character from first file */
string ch2; /*
character from second file */
if (argc != 3) return
2;
f1.open(argv[1],
ios::in);
if (f1.fail())
return 2;
f2.open(argv[2],
ios::in);
if (f2.fail())
return 2;
do
{ ch1
= get_char(f1);
ch2 = get_char(f2);
if (ch1 != ch2) return 1;
} while (!f1.eof()
&& !f2.eof());
return 0;
}
Type in the following commands:
copy hello.cpp hello.bak
fcomp hello.cpp hello.bak
The following batch file,
named filecomp.bat, can test the return value of main.
@echo off
fcomp %1 %2
if errorlevel 2 goto error
if errorlevel 1 goto different
echo %1 and %2 are identical
goto end
:different
echo %1 and %2 are different
goto end
:error
echo %1 or %2 does not exist
:end
The test "if errorlevel 1" really means "if the error level is >= 1". For that reason, we first have to test for error level 2, then for error level 1.
Run the following:
filecomp hello.cpp hello.bak
filecomp hello.cpp fcomp.cpp
filecomp yellow.cpp fcomp.cpp
We now go on to make a really
useful batch file, for backing up your work every day. If you work in more
than one place, you carry floppies with your files. You copy those files
from floppies to hard disks on the computers that you use. You end up with
different versions of the same file. If you are not careful, you can easily
copy an older version onto a newer version of the same file, maybe wiping
out several hours of work.
What we need is a mechanism that copies newer files onto older files but never older files over newer files.
DOS keeps track of the time that a file was last modified. You can see this time in the dir command.
The program timecomp.cpp
(timecomp.exe is also supplied) compares the times of last modification
of two files. It returns :
3 if one of the files doesn't
exist
2 if the first file is newer
than the second
1 if the files have the same
age
0 if the first file is older
than the second
We only want to copy a file if it is newer than the target, or if the target doesn't exist. That is, we want to test for error level >= 2. The following batch file, backfile.bat, does that:
@echo off
timecomp %1 %2
if errorlevel 2 goto copy
echo Skipping %1
goto end
:copy
echo Backing up %1
copy %1 %2 > nul
:end
Try it out. Put a disk in drive a: and enter the following commands:
backfile hello.cpp a:hello.cpp
Now edit hello.cpp to print "Hello, cruel world" and enter the commands
backfile a:hello.cpp hello.cpp
backfile hello.cpp a:hello.cpp
backfile.bat shows
some fancy batch file techniques. Note that we can use the argument %1
in the echo command. The "> nul" in the copy command
sends the line "1 file(s) copied" to the special device nul rather
than the display. Anything sent to nul just disappears.
So far, backfile is a safer version of copy. It will only copy a new file onto an older one. To make it really convenient to do a backup, here is another file backall.bat that backs up all *.cpp *.h, *.txt, and *.bat files.
@echo off
for %%f in (A,a,B,b,C,c,J,j)
do if "%%f" == "%1" goto ok
echo Usage: backall drive
echo Example: backall a
goto end
:ok
for %%f in (*.cpp) do call backfile
%%f %1:%%f
for %%f in (*.h) do call backfile
%%f %1:%%f
for %%f in (*.txt) do call backfile
%%f %1:%%f
for %%f in (*.bat) do call backfile
%%f %1:%%f
:end
Try it out:
backall a
Do it again:
backall a
Do the following:
del a:hello.*
backall a
backall.bat shows
more fancy batch file technique. There are two forms of the for
loop. for var in (list) steps through all items in the list. for
var in (pattern) steps through all files matching the pattern. In a
batch file, var is always of the form %%letter. To call one
batch file from another, you must use call.
You should really use backall. When you start working, switch to the a: drive and do a backall j. When you are done, do a backall a from the j: drive. It is a quick way of keeping your files synchronized.
grep
The grep program is an indispensable utility to quickly look inside a number of files. Unfortunately, it is not a standard part of DOS, so we supply it as an executable file grep.com in the lab6 files. We'll start with a simple example.
Make sure your current directory is j:\cs46a\lab\lab6 and type
grep errorlevel *.bat
This is the simplest, and
most common usage of grep. You get a list of all lines containing
the string pattern (the first argument to grep), in all matching
files (the second argument to grep).
A very common use for grep is to answer the question "Which !@#$ header file must I include to use a standard C++ definition"?
You want to find the directory containing the standard header files (e.g. stdlib.h, iostream.h, math.h, etc.). Try the following (do this for all defined drives on your system except a: and b:) :
dir /s c:iostream.h
dir /s j:iostream.h
dir /s k:iostream.h
Now let us find which header
defines the random number generator rand:
grep rand ??\*.h
(Replace the double question mark with the drive and directory that you found in the previous step.)
The name "grep" stands
for "global regular expression print". That means we can look for regular
expressions, not just patterns. One of the most useful regular expressions
is the one below, to scan a C++ file for "magic numbers".
Try
grep [^A-Za-z][0-9]+ timecomp.cpp
The syntax for regular expressions
is not easy to remember, and there is no glory in trying to. The grep
program will give you help on its arguments and the regular expression
syntax.
Find the help screen. (Hint: Type grep with no arguments.)
Sometimes grep prints
so many lines that the output is overwhelming.
To find the definitions of max or min, try
grep m[ai][xn] ??\*.h
(Again, replace the double question mark with the directory containing the C++ header files that you found previously in this lab.)
Whoa! That was too much. Of course, you can use
grep m[ai][xn] ??\*.h | more
to look at the output a screen at a time. But in this situation, one would like to first narrow down the selection of files. The grep output lists the files in the format "File ...\string.h". We'd like to just extract the lines starting with the word "File" from the grep output. And we know a tool to do that extraction, namely grep!
Try
grep m[ai][xn] n:\include\*.h | grep ^File | more
Date __________________
Initials_________________