[cwd]>
', where cwd
is the "current working
directory," and executes commands until the exit
command is
entered. In the loop, the shell checks if the command is one of the
built-in commands (given below) or an executable (external command) in the search
path. The shell needs to pass arguments to the external commands (i.e.,
build an argv
array). If the command entered is neither
one of the built-in commands nor in the search paths, the shell prints
"command
: Command not found." where command
is the
command name that was entered.
fgets(3)
to read in the (entire)
command line, then use strtok(3)
with a space as the delimiter.
The first "word" will be the command (where we'll ignore the possibility that a
command could have a space in its name). All the "words" after that are
arguments to be passed to the command (which the shell needs to put into a char**
).
Check out parse-cmd.c
to see how this is done.
fgets()
returns NULL on error or when end
of file (EOF) occurs while no characters have been read.
/
') or a path begins with './
', '../
', etc., and run that if it is an executable
(use access(2)
to check).get_path.c
in the
skeleton code. (You may
gcc get_path.c get_path_main.c
and run the
generated a.out
to see what happends.)
You may also use your own linked list code.
Use access(2)
in a loop to find the first executable
in the paths for the command.
snprintf(3)
would be useful here (as using strcat()
has caused problems for some people).execve(2)
.
Review execve_ls.c to see
how execve(2)
should be invoked.
The shell should also execute
waitpid(2)
and print out the return status of the command if
it is nonzero like tcsh
does when the
printexitvalue
shell variable is set. Look into using the
WEXITSTATUS macro from
<sys/wait.h>.*
wildcard character when a single *
is given.
You do not need to handle the situation when *
is given with a /
in the
string (i.e., /usr/include/*.h).
This should work just like it does in csh/tcsh when noglob
is not set.
The shell need only to support the
possibility of one *
on the command line, but it could have characters
prepended and/or appended. (That is, ls *
should work correctly
as should ls *.c
, ls s*
, ls
p*.txt
, etc.) Hint: implement
the list
built-in command explained below before attempting this.
You may use glob(3)
or wordexp(3)
if you wish.
[Review glob.c
in
sample code.]
Note that it is the shell's responsibility
to expand wildcards to matching (file) names.
(Review wildcard-1.c
in
sample code.)
If there were no matches, the "original"
arguments are passed to execve()
.
The shell would
only need to make the wildcard to work with external commands.
signal(2)
and/or
sigset(3)
for this. Ctrl-Z (SIGTSTP) and SIGTERM
should be ignored using sigignore(3)
or
signal(2)
. Check out sample code
sleep_Z.c.
Note that when a user is running a command
(executable) inside
the shell and presses control-C, signal SIGINT is sent to both
the shell process and the running command process
(i.e., all the processes in the foreground
process group).
(Review Sections 9.4 (Process Groups), 9.5 (Sessions), and
9.6 (Controlling Terminal) of Stevens and Rago's
APUE book for details.)
ignoreeof
tcsh shell variable is set, i.e., ignore it,
instead of exiting or seg-faulting. Note that Ctrl-D is not a signal,
but the EOF char. If ignoreeof
is set to the empty string or '0' and the input device is a terminal, the end-of-file command (usually generated by the user by typing '^D' on an empty line)
causes the shell to print 'Use "exit" to leave tcsh.' instead of exiting. This prevents the shell from accidentally being killed. If set to a number n, the shell ignores n - 1 consecutive end-of-files and exits on the nth.
If unset, '1' is used, i.e. the shell exits on a single '^D'.
Review all the
shell variables of tcsh.
(Please review the difference between
Shell Variables and Environment Variables.)
You may also review
this code to see
how fgets()
works with EOF. perror(3)
as needed. Also
avoid memory leaks by calling free(3)
as needed.exit
- obvious!which
- same as the tcsh one (hint: you can write a
function to do this and use it for finding the command to execute)
[Review get_path_main.c
in
shell skeleton code for details]where
- same as the tcsh one (reports all
known instances of command in path)
[Review get_path_main.c
in
shell skeleton code]cd
- chdir(2)
to the directory given; with no arguments,
chdir to the home directory; with a '-' as the only argument, chdirs to directory
previously in, the same as what tcsh does. [Review Stevens and Rago's Section 4.23 for details and the reason why cd has to be implemented as a built-in command]pwd
- print the current working directory.
[Review getcwd.c
in
sample code;
review Stevens and Rago's Section 4.23 for details]list
- with no arguments, lists the files in the
current working directory one per line. With arguments, lists the files in
each directory given as an argument, with a blank line then the name
of the directory followed by a : before the list of files in that
directory. You will need to use opendir(3)
and
readdir(3)
. (Hint: read their respective man pages carefully,
and refer to Fig. 1.3 of Stevens and Rago's APUE book)pid
- prints the pid of the shellkill
- When given just a pid, sends a SIGTERM to
the process with that pid using kill(2)
. When given a signal number (with a -
in front of it), sends that signal to the pid.
(e.g., kill 5678
, kill -9 5678
).
Read the man page of kill(2)
to check for
return value of -1 on error and the 'errno' value of ESRCH (3)
for "No such process."
prompt
- When ran with no arguments, prompts for
a new prompt prefix string. When given an argument make that the
new prompt prefix. For instance, let's assume cwd
is /usa/cshen
.[/usa/cshen]> prompt CISC361 CISC361 [/usa/cshen]> _ CISC361 [/usa/cshen]> cd 361 CISC361 [/usa/cshen/361]> prompt YES YES [/usa/cshen/361]> prompt input prompt prefix: hello hello [/usa/cshen/361]> _
printenv
- Should work the same as the tcsh
built-in one. When ran with no arguments, prints the whole environment.
(This can be done in 2 lines of code, a printf()
inside
of a while
loop by accessing the extern
environ
variable, not counting a variable
declaration).
When ran with one argument, call getenv(3)
on it. When
called with two or more args, print the same error message to stderr
that tcsh does.
(Review Stevens and Rago's Section 7.5 Environment List [and
Section 7.4 Command-Line Arguments] for details.) setenv
-
Should work the same as the tcsh built-in one. When ran with no
arguments, prints the whole environment, the same as
printenv
. When ran with one argument, sets that as
an empty environment variable. When ran with two arguments, the
second one is the value of the first. When ran with more args,
print the same error message to stderr that tcsh does. You can
use the setenv(3)
function for this command.
Special care must be given when PATH and HOME are changed. When
PATH is changed, be sure to update your linked list for the path
directories (and free()
up the old one). When HOME is changed,
cd
with no arguments should now go to the new home.
(Review Stevens and Rago's Section 7.9 Environment Variables for details.) which
first. Then you will be able to create a new process
with fork(2)
and use execve(2)
in the child process
and waitpid(2)
in the parent. Then handle arguments and do
the other built-ins. Remember to the read man pages for
system and library calls, include the corresponding header files.
fork(2)
and exec(2)
can be found here.
atoi(3), fprintf(3), index(3), calloc(3), malloc(3),
memcpy(3), memset(3), getcwd(3), strncmp(3), strlen(3).
which
and where
which
and where
commands work is to use the
following 3 programs (that are part of the Shell skeleton code) to
create an executable as follows.
get_path.c get_path.h get_path_main.c
$ gcc get_path.c get_path_main.c $ ./a.outIn tcsh, you can append a directory called /usr/local/bin to the END of search path by entering the following command and then print the new value.
$ set path = ($path /usr/local/bin) $ echo $pathTo add the directory to the FRONT of search path, do
$ set path = (/usr/local/bin $path)In bash, to add the directory to the END of search path, do
$ export PATH=$PATH:/usr/local/binTo add to the FRONT of path, do
$ export PATH=/usr/local/bin:$PATHBoth
which
and where
will start searching for the target executable from the front of the path/PATH value.
[return] Ctrl-D Ctrl-Z Cotrl-C which ; test which which ls ls ; execute it [return] Ctrl-D ; make sure still work Ctrl-Z Ctrl-C ls -l ; test passing arguments ls -l -a /proc/tty ls -l -F /proc/tty ls -l -F /etc/perl ls -l -a /etc/perl where ; test where where ls /bin/ls -l -g ; test absolutes and passing args /bin/ls -l file * ; test out * wildcard ls * ls *.c ls -l sh.* ls -l s*.c ls -l s*h.c blah ; try a command that doesn't exist /blah ; an absolute path that doesn't exist ls -l /blah /usr/bin ; try to execute a directory /bin/ls -la / file /bin/ls /bin/rm which ls rm ; test multiple args where ls rm list ; test list list / /usr/bin cd ; cd to home pwd cd /blah ; non-existant cd /usr/bin /usr/ucb ; too many args cd - ; should go back to project dir pwd more sh.c (and give a Crtl-C) ; more should exit cd /usr/bin pwd ./ls / ; test more absolutes ../bin/ls / pid ; get pid for later use kill kill pid-of-shell ; test default kill -1 pid-of-shell ; test sending a signal, should kill ; the shell, so restart a new one prompt (and enter some prompt prefix) prompt 361shell printenv PATH printenv setenv setenv TEST printenv TEST setenv TEST testing printenv TEST setenv TEST testing more setenv HOME / cd pwd exit
valgrind --leak-check=full
to check for any memory leaks.
You need to tar up your source code, test run script file, etc. To do this, do the following.
proj3
to store all your programs.
tar cvf YourName_proj3.tar proj3
tar tvf YourName_proj3.tar