diff --git a/Makefile b/Makefile index 2584e4a..08849c6 100644 --- a/Makefile +++ b/Makefile @@ -116,6 +116,7 @@ mkfs/mkfs: mkfs/mkfs.c $K/fs.h $K/param.h .PRECIOUS: %.o UPROGS=\ + $U/_test\ $U/_cat\ $U/_echo\ $U/_forktest\ diff --git a/user/cat.c b/user/cat.c index 598f005..8382b18 100644 --- a/user/cat.c +++ b/user/cat.c @@ -4,39 +4,136 @@ char buf[512]; -void -cat(int fd) -{ +//flag variables +uint8 nflag = 0; +uint8 vflag = 0; +uint8 bflag = 0; +uint8 Eflag = 0; + +/** + * @cat Display file content with various flags. + * + * This function reads the content of a file descriptor and displays it + * according to the specified flags. It supports line numbering, showing + * non-printing characters, numbering non-blank lines, and displaying + * end-of-line markers. + * + * @param fd The file descriptor to read from. + * @param line_numbering If non-zero, number all output lines. + * @param show_non_printing If non-zero, show non-printing characters in caret notation. + * @param number_non_blank If non-zero, number only non-blank lines. + * @param show_end_of_line If non-zero, display '$' at the end of each line. + */ + +void cat(int fd, int line_numbering, int show_non_printing, int number_non_blank, int show_end_of_line) { int n; + int line_number = 1; + int i; + int is_blank_line = 1; - while((n = read(fd, buf, sizeof(buf))) > 0) { - if (write(1, buf, n) != n) { - fprintf(2, "cat: write error\n"); - exit(1); + //read the file content in chunks + while ((n = read(fd, buf, sizeof(buf))) > 0) { + for (i = 0; i < n; i++) { + //check if the current character is part of a blank line + if (buf[i] != '\n' && buf[i] != '\r' && buf[i] != '\t' && buf[i] != ' ') { + is_blank_line = 0; + } + + //add line numbers if the -n or -b flag is set + if ((line_numbering || (number_non_blank && !is_blank_line)) && (i == 0 || buf[i-1] == '\n')) { + printf("%6d ", line_number++); + } + + //convert non-printing characters to a visible format if the -v flag is set + //tested externally with test.c amd ran with OS as test (user program) + if (show_non_printing && (buf[i] < 32 || buf[i] == 127)) { + if (buf[i] == 127) { + printf("^?"); + } else { + printf("^%c", buf[i] + 64); + } + } else { + //write the character to the output + if (write(1, &buf[i], 1) != 1) { + fprintf(2, "cat: write error\n"); + exit(1); + } + } + + //display $ at the end of each line if the -E flag is set + if (show_end_of_line && buf[i] == '\n') { + if (!number_non_blank || !is_blank_line) { + printf("$"); + } + } + + //reset blank line flag at the end of each line + if (buf[i] == '\n') { + is_blank_line = 1; + } } } - if(n < 0){ + + //handle read errors (unchanged) + if (n < 0) { fprintf(2, "cat: read error\n"); exit(1); } } -int -main(int argc, char *argv[]) -{ +int main(int argc, char *argv[]) { int fd, i; + int line_numbering = 0; + int show_non_printing = 0; + int number_non_blank = 0; + int show_end_of_line = 0; + + //parse command-line arguments for flags + for (i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + for (int j = 1; argv[i][j] != '\0'; j++) { //allows handling multiple flags in a single argument + switch (argv[i][j]) { + case 'n': + nflag = 1; + break; + case 'v': + vflag = 1; + break; + case 'b': + bflag = 1; + break; + case 'E': + Eflag = 1; + break; + default: + fprintf(2, "Unrecognized flag: -%c\n", argv[i][j]); + exit(1); + } + } + } else { + break; + } + } + + //set the flag variables + line_numbering = nflag; + show_non_printing = vflag; + number_non_blank = bflag; + show_end_of_line = Eflag; - if(argc <= 1){ - cat(0); + //if no files are specified, read from standard input + if (i == argc) { + cat(0, line_numbering, show_non_printing, number_non_blank, show_end_of_line); exit(0); } - for(i = 1; i < argc; i++){ - if((fd = open(argv[i], 0)) < 0){ + //process each file specified in the command-line arguments (unchanged, except for, for and cat args) + for (; i < argc; i++) { + if ((fd = open(argv[i], 0)) < 0) { fprintf(2, "cat: cannot open %s\n", argv[i]); exit(1); } - cat(fd); + cat(fd, line_numbering, show_non_printing, number_non_blank, show_end_of_line); close(fd); } exit(0); diff --git a/user/test.c b/user/test.c new file mode 100644 index 0000000..fa3b40f --- /dev/null +++ b/user/test.c @@ -0,0 +1,47 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + + +//function to convert non-printing characters to caret notation +//same function as the -v flag, to test +void print_with_caret_notation(const char *str) { + for (int i = 0; i < strlen(str); i++) { + if (str[i] < 32 || str[i] == 127) { + if (str[i] == 127) { + printf("^?"); + } else { + printf("^%c", str[i] + 64); + } + } else { + printf("%c", str[i]); + } + } + printf("\n"); +} + +int main() { + //test cases with non-printing characters + const char *test1 = "Hello\001World"; // SOH (ASCII 1) + const char *test2 = "Tab\tCharacter"; // TAB (ASCII 9) + const char *test3 = "New\nLine"; // LF (ASCII 10) + const char *test4 = "Carriage\rReturn"; // CR (ASCII 13) + const char *test5 = "Delete\177Char"; // DEL (ASCII 127) + + printf("Test 1: "); + print_with_caret_notation(test1); + + printf("Test 2: "); + print_with_caret_notation(test2); + + printf("Test 3: "); + print_with_caret_notation(test3); + + printf("Test 4: "); + print_with_caret_notation(test4); + + printf("Test 5: "); + print_with_caret_notation(test5); + + return 0; +} diff --git a/user/test.txt b/user/test.txt new file mode 100644 index 0000000..4ebbef5 --- /dev/null +++ b/user/test.txt @@ -0,0 +1,13 @@ +Hello, World! +This is a test file. + +It contains some non-printing characters. +Here is a tab: <- tab character +Here is a newline: + +Here is a carriage return: +<- carriage return character +Here is a bell character:<- bell character +Here is a delete character:<- delete character +Here is a form feed character:<- form feed character +Here is a vertical tab character:<- vertical tab character