Skip to content
Snippets Groups Projects
Commit 91196bcd authored by cxb23's avatar cxb23
Browse files

finished assignment 3-SHellP1

parent 51b12715
Branches
No related tags found
No related merge requests found
File added
File added
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dshlib.h"
void printDragon() {
printf("[DRAGON for extra credit would print here]\n");
}
/*
* Implement your main function by building a loop that prompts the
* user for input. Use the SH_PROMPT constant from dshlib.h and then
* use fgets to accept user input. Since we want fgets to also handle
* end of file so we can run this headless for testing we need to check
* the return code of fgets. I have provided an example below of how
* to do this assuming you are storing user input inside of the cmd_buff
* variable.
*
* while(1){
* printf("%s", SH_PROMPT);
* if (fgets(cmd_buff, ARG_MAX, stdin) == NULL){
* printf("\n");
* break;
* }
* //remove the trailing \n from cmd_buff
* cmd_buff[strcspn(cmd_buff,"\n")] = '\0';
*
* //IMPLEMENT THE REST OF THE REQUIREMENTS
* }
*
* Also, use the constants in the dshlib.h in this code.
* SH_CMD_MAX maximum buffer size for user input
* EXIT_CMD constant that terminates the dsh program
* SH_PROMPT the shell prompt
* OK the command was parsed properly
* WARN_NO_CMDS the user command was empty
* ERR_TOO_MANY_COMMANDS too many pipes used
*
* Expected output:
*
* CMD_OK_HEADER if the command parses properly. You will
* follow this by the command details
*
* CMD_WARN_NO_CMD if the user entered a blank command
* CMD_ERR_PIPE_LIMIT if the user entered too many commands using
* the pipe feature, e.g., cmd1 | cmd2 | ... |
*
* See the provided test cases for output expectations.
*/
int main()
{
char *cmd_buff = malloc(ARG_MAX * sizeof(char));
int rc = OK;
command_list_t *clist = malloc(sizeof(command_list_t));
clist->num = 0;
while (1)
{
printf("%s", SH_PROMPT);
if (fgets(cmd_buff, ARG_MAX, stdin) == NULL)
{
printf("\n");
break;
}
// remove the trailing \n from cmd_buff
cmd_buff[strcspn(cmd_buff, "\n")] = '\0';
// IMPLEMENT THE REST OF THE REQUIREMENTS
if (strcmp(cmd_buff, EXIT_CMD) == 0) {
exit(rc);
}
if (strcmp(cmd_buff, "dragon") == 0) {
printDragon();
continue;
}
if (strcmp(cmd_buff, "\0") == 0) {
rc = WARN_NO_CMDS;
printf(CMD_WARN_NO_CMD);
continue;
}
rc = build_cmd_list(cmd_buff, clist);
if (rc == ERR_TOO_MANY_COMMANDS) {
printf(CMD_ERR_PIPE_LIMIT, CMD_MAX);
continue;
}
printf(CMD_OK_HEADER, clist->num);
for (int i = 0; i < clist->num; i++) {
if (strlen(clist->commands[i].args) > 0) {
printf("<%d> %s [%s]\n", i + 1, clist->commands[i].exe, clist->commands[i].args);
} else {
printf("<%d> %s\n", i + 1, clist->commands[i].exe);
}
}
}
free(cmd_buff);
free(clist);
return rc;
}
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "dshlib.h"
int numInstanceOf(char *str, const char c) {
int count = 0;
while (*str != '\0') {
if (*str == c) count++;
str++;
}
return count;
}
/*
* build_cmd_list
* cmd_line: the command line from the user
* clist *: pointer to clist structure to be populated
*
* This function builds the command_list_t structure passed by the caller
* It does this by first splitting the cmd_line into commands by spltting
* the string based on any pipe characters '|'. It then traverses each
* command. For each command (a substring of cmd_line), it then parses
* that command by taking the first token as the executable name, and
* then the remaining tokens as the arguments.
*
* NOTE your implementation should be able to handle properly removing
* leading and trailing spaces!
*
* errors returned:
*
* OK: No Error
* ERR_TOO_MANY_COMMANDS: There is a limit of CMD_MAX (see dshlib.h)
strcat(command.args, inner_token); * commands.
* ERR_CMD_OR_ARGS_TOO_BIG: One of the commands provided by the user
* was larger than allowed, either the
* executable name, or the arg string.
*
* Standard Library Functions You Might Want To Consider Using
* memset(), strcmp(), strcpy(), strtok(), strlen(), strchr()
*/
int build_cmd_list(char *cmd_line, command_list_t *clist)
{
if (numInstanceOf(cmd_line, PIPE_CHAR) > CMD_MAX-1) return ERR_TOO_MANY_COMMANDS;
if ((int)strlen(cmd_line) > SH_CMD_MAX) return ERR_CMD_OR_ARGS_TOO_BIG;
clist->num = 0;
char *outer_saveptr = NULL;
char *inner_saveptr = NULL;
char *outer_token = strtok_r(cmd_line, PIPE_STRING, &outer_saveptr);
while (outer_token != NULL) {
if (clist->num > CMD_MAX) return ERR_TOO_MANY_COMMANDS;
command_t command;
memset(&command, 0, sizeof(command_t));
char *inner_token = strtok_r(outer_token, SPACE_STRING, &inner_saveptr);
if (inner_token == NULL) {
outer_token = strtok_r(NULL, PIPE_STRING, &outer_saveptr);
continue;
}
if (strlen(inner_token) > EXE_MAX) return ERR_CMD_OR_ARGS_TOO_BIG;
strcpy(command.exe, inner_token);
command.args[0] = '\0';
inner_token = strtok_r(NULL, SPACE_STRING, &inner_saveptr);
while (inner_token != NULL) {
if (strlen(command.args) + strlen(inner_token) + 1 > ARG_MAX) return ERR_CMD_OR_ARGS_TOO_BIG;
if (strlen(command.args) > 0) strcat(command.args, SPACE_STRING);
strcat(command.args, inner_token);
inner_token = strtok_r(NULL, SPACE_STRING, &inner_saveptr);
}
clist->commands[clist->num] = command;
clist->num++;
outer_token = strtok_r(NULL, PIPE_STRING, &outer_saveptr);
}
return OK;
}
#ifndef __DSHLIB_H__
#define __DSHLIB_H__
// Constants for command structure sizes
#define EXE_MAX 64
#define ARG_MAX 256
#define CMD_MAX 8
// Longest command that can be read from the shell
#define SH_CMD_MAX EXE_MAX + ARG_MAX
typedef struct command
{
char exe[EXE_MAX];
char args[ARG_MAX];
} command_t;
typedef struct command_list
{
int num;
command_t commands[CMD_MAX];
} command_list_t;
// Special character #defines
#define SPACE_CHAR ' '
#define SPACE_STRING " "
#define PIPE_CHAR '|'
#define PIPE_STRING "|"
#define SH_PROMPT "dsh> "
#define EXIT_CMD "exit"
// Standard Return Codes
#define OK 0
#define WARN_NO_CMDS -1
#define ERR_TOO_MANY_COMMANDS -2
#define ERR_CMD_OR_ARGS_TOO_BIG -3
// starter code
#define M_NOT_IMPL "The requested operation is not implemented yet!\n"
#define EXIT_NOT_IMPL 3
#define NOT_IMPLEMENTED_YET 0
// prototypes
int build_cmd_list(char *cmd_line, command_list_t *clist);
// output constants
#define CMD_OK_HEADER "PARSED COMMAND LINE - TOTAL COMMANDS %d\n"
#define CMD_WARN_NO_CMD "warning: no commands provided\n"
#define CMD_ERR_PIPE_LIMIT "error: piping limited to %d commands\n"
#endif
# Compiler settings
CC = gcc
CFLAGS = -Wall -Wextra -g
# Target executable name
TARGET = dsh
# Find all source and header files
SRCS = $(wildcard *.c)
HDRS = $(wildcard *.h)
# Default target
all: $(TARGET)
# Compile source to executable
$(TARGET): $(SRCS) $(HDRS)
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
# Clean up build files
clean:
rm -f $(TARGET)
test:
./test.sh
# Phony targets
.PHONY: all clean
#!/usr/bin/env bats
@test "Simple Command" {
run ./dsh <<EOF
test_command
exit
EOF
# Strip all whitespace (spaces, tabs, newlines) from the output
stripped_output=$(echo "$output" | tr -d '[:space:]')
# Expected output with all whitespace removed for easier matching
expected_output="dsh>PARSEDCOMMANDLINE-TOTALCOMMANDS1<1>test_commanddsh>"
# These echo commands will help with debugging and will only print
#if the test fails
echo "Captured stdout:"
echo "Output: $output"
echo "Exit Status: $status"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
# Assertions
[ "$status" -eq 0 ]
}
@test "Simple Command with Args" {
run ./dsh <<EOF
cmd -a1 -a2
exit
EOF
# Strip all whitespace (spaces, tabs, newlines) from the output
stripped_output=$(echo "$output" | tr -d '[:space:]')
# Expected output
expected_output="dsh>PARSEDCOMMANDLINE-TOTALCOMMANDS1<1>cmd[-a1-a2]dsh>"
# These echo commands will help with debugging and will only print
#if the test fails
echo "Captured stdout:"
echo "Output: $output"
echo "Exit Status: $status"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
# Assertions
[ "$status" -eq 0 ]
}
@test "No command provided" {
run ./dsh <<EOF
exit
EOF
# Strip all whitespace (spaces, tabs, newlines) from the output
stripped_output=$(echo "$output" | tr -d '[:space:]')
# Expected output with all whitespace removed for easier matching
expected_output="dsh>warning:nocommandsprovideddsh>"
# These echo commands will help with debugging and will only print
#if the test fails
echo "Captured stdout:"
echo "Output: $output"
echo "Exit Status: $status"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
# Assertions
[ "$status" -eq 0 ]
}
@test "Two commands" {
run ./dsh <<EOF
command_one | command_two
exit
EOF
# Strip all whitespace (spaces, tabs, newlines) from the output
stripped_output=$(echo "$output" | tr -d '[:space:]')
# Expected output with all whitespace removed for easier matching
expected_output="dsh>PARSEDCOMMANDLINE-TOTALCOMMANDS2<1>command_one<2>command_twodsh>"
# These echo commands will help with debugging and will only print
#if the test fails
echo "Captured stdout:"
echo "Output: $output"
echo "Exit Status: $status"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
# Assertions
[ "$status" -eq 0 ]
}
@test "three commands with args" {
run ./dsh <<EOF
cmd1 a1 a2 a3 | cmd2 a4 a5 a6 | cmd3 a7 a8 a9
exit
EOF
# Strip all whitespace (spaces, tabs, newlines) from the output
stripped_output=$(echo "$output" | tr -d '[:space:]')
# Expected output with all whitespace removed for easier matching
expected_output="dsh>PARSEDCOMMANDLINE-TOTALCOMMANDS3<1>cmd1[a1a2a3]<2>cmd2[a4a5a6]<3>cmd3[a7a8a9]dsh>"
# These echo commands will help with debugging and will only print
#if the test fails
echo "Captured stdout:"
echo "Output: $output"
echo "Exit Status: $status"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
# Assertions
[ "$status" -eq 0 ]
}
@test "try max (8) commands" {
run ./dsh <<EOF
cmd1 | cmd2 | cmd3 | cmd4 | cmd5 | cmd6 | cmd7 | cmd8
exit
EOF
# Strip all whitespace (spaces, tabs, newlines) from the output
stripped_output=$(echo "$output" | tr -d '[:space:]')
# Expected output with all whitespace removed for easier matching
expected_output="dsh>PARSEDCOMMANDLINE-TOTALCOMMANDS8<1>cmd1<2>cmd2<3>cmd3<4>cmd4<5>cmd5<6>cmd6<7>cmd7<8>cmd8dsh>"
# These echo commands will help with debugging and will only print
#if the test fails
echo "Captured stdout:"
echo "Output: $output"
echo "Exit Status: $status"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
# Assertions
[ "$status" -eq 0 ]
}
@test "try too many commands" {
run ./dsh <<EOF
cmd1 | cmd2 | cmd3 | cmd4 | cmd5 | cmd6 | cmd7 | cmd8 | cmd9
exit
EOF
# Strip all whitespace (spaces, tabs, newlines) from the output
stripped_output=$(echo "$output" | tr -d '[:space:]')
# Expected output with all whitespace removed for easier matching
expected_output="dsh>error:pipinglimitedto8commandsdsh>"
# These echo commands will help with debugging and will only print
#if the test fails
echo "Captured stdout:"
echo "Output: $output"
echo "Exit Status: $status"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
# Assertions
[ "$status" -eq 0 ]
}
@test "kitchen sink - multiple commands" {
run ./dsh <<EOF
cmd1
cmd2 arg arg2
p1 | p2
p3 p3a1 p3a2 | p4 p4a1 p4a2
EOF
# Strip all whitespace (spaces, tabs, newlines) from the output
stripped_output=$(echo "$output" | tr -d '[:space:]')
# Expected output with all whitespace removed for easier matching
expected_output="dsh>PARSEDCOMMANDLINE-TOTALCOMMANDS1<1>cmd1dsh>PARSEDCOMMANDLINE-TOTALCOMMANDS1<1>cmd2[argarg2]dsh>PARSEDCOMMANDLINE-TOTALCOMMANDS2<1>p1<2>p2dsh>PARSEDCOMMANDLINE-TOTALCOMMANDS2<1>p3[p3a1p3a2]<2>p4[p4a1p4a2]dsh>"
# These echo commands will help with debugging and will only print
#if the test fails
echo "Captured stdout:"
echo "Output: $output"
echo "Exit Status: $status"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
# Assertions
[ "$status" -eq 0 ]
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment