I'm working on a class project in which I must write a command line shell with the following requirements:
- The shell must able to read buffered input
- Buffer should be 64 characters
- Error conditions should be handled
- Exceeded buffer size
- Interruptions (when a signal arrives) – see the man page for read()
- Invalid input (unparsable characters, blank lines, etc)
- Any other error that may be encountered.
- Shell must have a history of at least 20 items, and the history must not be of a static size. When the history buffer is full, the oldest item is removed and the newest item added.
- Programs should be able to run in the foreground or background. (using &)
- Ctrl-D will exit the shell
- Ctrl-C will print the complete history
- The Command ‘history’ will also print the complete history. Newest items will be at the bottom of the list.
- All other signals will be trapped and displayed to the user in the shell
- Program will use the read() command to read in input, unless the arrow keys are supported
I have opted to implement arrow keys for history cycling, so I'm using ncurses for input, rather than read(). I think I'm doing all right using strtok() to parse input, and fork() and execvp() to run the processes, but I'm not doing all right implementing ncurses correctly. All I've gotten it to do so far is init a new screen, display the prompt, then segfault upon any key press. Not good.
I reckon the problem must be in my design. I'm not wrapping my head around ncurses too well. What sort of data structures should I be using for this project? How should I handle the ncurses setup, teardown, and everything in between? What's the deal with windows and screens, and should I have a single globally accessible window/screen that I work with? Also, I've been trying to use a char* for the input buffer, and a char** for the command history, but I have no experience in C, so despite reading up on malloc, calloc, and realloc, I'm not sure of the best way to store commands in the buffer and the history. Any tips on managing these char arrays?
tl;dr: How do I use ncurses correctly to make a command line shell, and how do I handle the command memory management with C?
I realize this is a pretty hefty question. :(
edit: I have already seen http://www.gnu.org/software/libc/manual/html_node/Implementing-a-Shell.html and http://www.linuxinfor.com/english/NCURSES-Programming/ but the ncurses documentation has actually too much overhead. I just want to use its ability to recognize arrow keys.
If your input buffer is defined to be 64 characters, then I would recommend using a
char
array instead of achar*
. Something likechar input_buffer[65];
should serve your purposes (add an extra character for the trailing'\0'
).As far as command history goes, you can use a two-dimensional array for that. Something like
char command_history[20][65];
should let you store 20 old commands of 64 characters each.Allocating these buffers statically should make things a bit easier for you, as you won't have to worry about
malloc
and friends.It's hard to give you too much specific advice without seeing your code. I have a feeling that you are making the same type of mistakes that are typical to people first learning C. Can you post the part of your code that is giving you problems so that we can learn more about what you are doing?
Update after posted provided code:
One problem I'm seeing is that the function
takeInput
doesn't have a return statement. When you useinput = takeInput();
inside your main function, the value ofinput
isn't being set to what you think it is. It's probably not a valid pointer, which is causing your line that saysinput[j]
to segfault.Your usage of
cmdHistory
also needs revisiting. You allocate it withcmdHistory = (char**)calloc(21,sizeof(int));
, which gives you enough space to store 21 integers. In the functionprintHistory
, you pass elements ofcmdHistory
toprintw
as if they were strings (they're only integers). This is most definitely not doing what you want it to do. Instead, your allocation logic forcmdHistory
needs to look more like your de-allocation logic (except backwards). Allocate an array ofchar**
, then iterate through the array, assigning each pointer to a newly-allocated buffer. Just like you have onefree
statement for each element in the array plus afree
for the array as a whole, you should have onemalloc
for each element plus onemalloc
for the array as a whole.Even if you can't use a statically-allocated stack, try writing your program using one anyway. This will let you work the kinks out of your key detection logic, etc without having to worry about the dynamic memory part of the program. Once the rest of it is working, go back in and swap out the static memory for dynamic memory allocation. That way, you're only having to debug a little bit at a time.
Here's some sample code which:
Performs dynamic memory allocation.
Reads from the console in non-blocking mode.
Uses VT100 codes to print a frame buffer to the console.
It compiles on Linux using GCC without warnings or errors. It's far from bug free, but it should give you some ideas of what's possible. Compile and run it, pressing [up] and [down] will print messages, typing characters and hitting [enter] will "execute" the command.
Have you looked at the Readline library? It's ideal for use in your project.
http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html