From 38c96e95be43fb1616437b271f32e57c1140423b Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 3 Aug 2026 13:30:34 +0100 Subject: [PATCH 01/20] set up argparse with -n, -n flags --- implement-shell-tools/cat/cat.py | 14 ++++++++++++++ implement-shell-tools/wc/requirements.txt | 1 + 2 files changed, 15 insertions(+) create mode 100644 implement-shell-tools/cat/cat.py create mode 100644 implement-shell-tools/wc/requirements.txt diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 000000000..2a6b69988 --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,14 @@ +import argparse + +parser = argparse.ArgumentParser( + prog="a simple cat implementation", + description="cat command line tool with the -n and -b flags" +) + +parser.add_argument("-n", action="store_true", help="number all output lines") +parser.add_argument("-b", action="store_true", help="number non-empty output lines") +parser.add_argument("paths", nargs="+", help="file path or paths", ) + +args = parser.parse_args(); + +print(args) \ No newline at end of file diff --git a/implement-shell-tools/wc/requirements.txt b/implement-shell-tools/wc/requirements.txt new file mode 100644 index 000000000..e9c6824d6 --- /dev/null +++ b/implement-shell-tools/wc/requirements.txt @@ -0,0 +1 @@ +argparse \ No newline at end of file From 0591f3f61428114bcf885c4905023cfeb2824aa5 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 3 Aug 2026 13:46:54 +0100 Subject: [PATCH 02/20] handle errors when reading file --- implement-shell-tools/cat/cat.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 2a6b69988..2335992fb 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -1,4 +1,5 @@ import argparse +import sys parser = argparse.ArgumentParser( prog="a simple cat implementation", @@ -11,4 +12,25 @@ args = parser.parse_args(); -print(args) \ No newline at end of file +# cat returns different error messages depending on the reason the path could be read +def cat_file(path): + try: + with open(path, "r",) as f: + content = f.read() + except FileNotFoundError: + print(f"cat: {path}: No such file or directory", file=sys.stderr) + return + except IsADirectoryError: + print(f"cat: {path}: Is a directory", file=sys.stderr) + return + except PermissionError: + print(f"cat: {path}: Permission denied", file=sys.stderr) + return + + print(content) + + + +for path in args.paths: + line_num = 1 + cat_file(path) \ No newline at end of file From 6020eef11e6daee88e9631e621a729e04ffe9e06 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 3 Aug 2026 13:55:46 +0100 Subject: [PATCH 03/20] return exit code 1 for any failed file read --- implement-shell-tools/cat/cat.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 2335992fb..32f66cfdd 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -19,18 +19,28 @@ def cat_file(path): content = f.read() except FileNotFoundError: print(f"cat: {path}: No such file or directory", file=sys.stderr) - return + return False except IsADirectoryError: print(f"cat: {path}: Is a directory", file=sys.stderr) - return + return False except PermissionError: print(f"cat: {path}: Permission denied", file=sys.stderr) - return + return False print(content) +# cat exits with error code 1 if any file read fails +file_error = False + for path in args.paths: line_num = 1 - cat_file(path) \ No newline at end of file + is_success = cat_file(path) + + if not is_success: + file_error = True + +# if at any point, file reading failed file error is set to True, +# and program exist with code 1 after all tasks completed +sys.exit(1 if file_error else 0) \ No newline at end of file From 108fef7cbe1ae35067cf1bc4db809f3dc238bde3 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 3 Aug 2026 14:19:19 +0100 Subject: [PATCH 04/20] seperate out file read and formatting --- implement-shell-tools/cat/cat.py | 64 ++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 32f66cfdd..8523a7301 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -13,34 +13,60 @@ args = parser.parse_args(); # cat returns different error messages depending on the reason the path could be read -def cat_file(path): +def read_file(path): + """Returns (content, error_message). error_message is None on success""" try: with open(path, "r",) as f: - content = f.read() + return f.read(), None except FileNotFoundError: - print(f"cat: {path}: No such file or directory", file=sys.stderr) - return False + return None, f"cat: {path}: No such file or directory" except IsADirectoryError: - print(f"cat: {path}: Is a directory", file=sys.stderr) - return False + return None, f"cat: {path}: Is a directory" except PermissionError: - print(f"cat: {path}: Permission denied", file=sys.stderr) - return False + return None, f"cat: {path}: Permission denied" - print(content) +# -b (number the non-empty lines) takes priority over -n (number all lines) +# if both are present +def format_lines(lines, number_all=False, number_non_empty=False): + """Returns a list of formatted output lines""" + output = [] + + if number_non_empty: + line_num = 0 + for line in lines: + if line == "": + output.append("") + else: + line_num += 1 + # {line_num:6} right justied number, length of at least 6 + # {some_str:6} left justifed string, length fo at least 6 + output.append(f"{line_num:6}\t{line}") + elif number_all: + for i, line in enumerate(lines, start=1): + output.append(f"{i:6}\t{line}") + else: + output = lines -# cat exits with error code 1 if any file read fails -file_error = False +# TODO: runner function to call read_file, and feed it into formatLines, then print -for path in args.paths: - line_num = 1 - is_success = cat_file(path) +def main(): + # cat exits with error code 1 if any file read fails + file_error = False - if not is_success: - file_error = True + for path in args.paths: + line_num = 1 + is_success = cat_file(path) -# if at any point, file reading failed file error is set to True, -# and program exist with code 1 after all tasks completed -sys.exit(1 if file_error else 0) \ No newline at end of file + if not is_success: + file_error = True + + # if at any point, file reading failed file error is set to True, + # and program exist with code 1 after all tasks completed + sys.exit(1 if file_error else 0) + +# ensures that main only runs when this file/module is directly executed +# not when it is imported, for example, for automated tests +if __name__ == "__main__": + main() \ No newline at end of file From 0c5aeb2d6be357cba8cb009176733e284cd081f6 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 3 Aug 2026 14:41:49 +0100 Subject: [PATCH 05/20] cat function to cat the formatted lines --- implement-shell-tools/cat/cat.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 8523a7301..b185e2471 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -28,11 +28,11 @@ def read_file(path): # -b (number the non-empty lines) takes priority over -n (number all lines) # if both are present -def format_lines(lines, number_all=False, number_non_empty=False): +def format_lines(lines, number_all=False, number_nonempty=False): """Returns a list of formatted output lines""" output = [] - if number_non_empty: + if number_nonempty: line_num = 0 for line in lines: if line == "": @@ -48,8 +48,29 @@ def format_lines(lines, number_all=False, number_non_empty=False): else: output = lines + return output + # TODO: runner function to call read_file, and feed it into formatLines, then print +def cat_file(path, number_all=False, number_nonempty=False): + """ + Calls read_file -> format_lines -> prints formatted line. + Returns True if file read successfully, else returns False + + If failed to read file, prints error to stderr + """ + content, error = read_file(path) + if (error): + print(error, file=sys.stderr) + return False + + # splitlines automatically trims trailing empty lines + lines = content.splitlines() + for line in format_lines(lines, number_all, number_nonempty): + print(line) + + return True + def main(): # cat exits with error code 1 if any file read fails @@ -57,7 +78,7 @@ def main(): for path in args.paths: line_num = 1 - is_success = cat_file(path) + is_success = cat_file(path, args.n, args.b) if not is_success: file_error = True From 9361dde008e9ed7e78f0bb76f0b1a0df7cdfb672 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Tue, 4 Aug 2026 22:47:46 +0100 Subject: [PATCH 06/20] remove stray semicolon --- implement-shell-tools/cat/cat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index b185e2471..57ae39682 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -10,7 +10,7 @@ parser.add_argument("-b", action="store_true", help="number non-empty output lines") parser.add_argument("paths", nargs="+", help="file path or paths", ) -args = parser.parse_args(); +args = parser.parse_args() # cat returns different error messages depending on the reason the path could be read def read_file(path): From 36177423416a70edb0245cb839b1bca06695974f Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Tue, 4 Aug 2026 22:57:44 +0100 Subject: [PATCH 07/20] set up parser --- implement-shell-tools/ls/ls.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 implement-shell-tools/ls/ls.py diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 000000000..40ab3a1ac --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,23 @@ +import argparse +import sys + +parser = argparse.ArgumentParser( + prog="a simple version of ls", + description="ls command line tool which can accept 0 or more arguements" \ + "and take -a and -1 flags") + +parser.add_argument("-a", action="store_true", help="show all files, including dot files") + +# can't store as an attribute of Namespace object, because 1 is not a valid python identifier +# but can store under the name given in the dest argument. When working with this +# parser, look for "opt_one", not "1". +parser.add_argument("-1", dest="opt_one", action="store_true", help="show one file/directory name per line") + +# takes 0 more arguments, if none are given, sets "." as default value +parser.add_argument("paths", nargs="*", help="file/directory path(s) to display", default=".") + +args = parser.parse_args() + +print(args) + +# def getDirectoryEntries(path, aFlag=args.a): From b28fa81af78f51ae003fd743ee1ecdaf2136e3e3 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Tue, 4 Aug 2026 23:36:46 +0100 Subject: [PATCH 08/20] read files/folders in a given folder --- implement-shell-tools/ls/ls.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index 40ab3a1ac..c51152cd3 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -1,5 +1,6 @@ import argparse import sys +import os parser = argparse.ArgumentParser( prog="a simple version of ls", @@ -18,6 +19,16 @@ args = parser.parse_args() -print(args) -# def getDirectoryEntries(path, aFlag=args.a): +def getDirectoryEntries(path, aFlag=args.a): + # warning: listdir() prints current directory by default + entries = os.listdir(path) + entries = [".", ".."] + entries + entries.sort() + + if (not args.a): + entries = [entry for entry in entries if not entry.startswith(".")] + return entries + +for path in args.paths: + print(getDirectoryEntries(path)) From 4f62b37e13fad7e42421579589304ea235b01e0d Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 00:02:13 +0100 Subject: [PATCH 09/20] function to format and print a single directory content --- implement-shell-tools/ls/ls.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index c51152cd3..3ca92d2f6 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -28,7 +28,19 @@ def getDirectoryEntries(path, aFlag=args.a): if (not args.a): entries = [entry for entry in entries if not entry.startswith(".")] + return entries +def printEntries(entries, onePerLineFlag = args.opt_one): + if (onePerLineFlag): + for entry in entries: + print(entry) + else: + for i in range(len(entries)-1): + print(f"{entries[i]}\t", end="") + print(entries[-1]) + + + for path in args.paths: - print(getDirectoryEntries(path)) + printEntries(getDirectoryEntries(path)) From 2ad95a6a25524a0c4f609f5f46da4f8d8a63be72 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 00:17:37 +0100 Subject: [PATCH 10/20] snake case function names --- implement-shell-tools/ls/ls.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index 3ca92d2f6..1afba00d5 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -20,7 +20,7 @@ args = parser.parse_args() -def getDirectoryEntries(path, aFlag=args.a): +def get_dir_entries(path, aFlag=args.a): # warning: listdir() prints current directory by default entries = os.listdir(path) entries = [".", ".."] + entries @@ -28,10 +28,10 @@ def getDirectoryEntries(path, aFlag=args.a): if (not args.a): entries = [entry for entry in entries if not entry.startswith(".")] - + return entries -def printEntries(entries, onePerLineFlag = args.opt_one): +def print_entries(entries, onePerLineFlag = args.opt_one): if (onePerLineFlag): for entry in entries: print(entry) @@ -41,6 +41,5 @@ def printEntries(entries, onePerLineFlag = args.opt_one): print(entries[-1]) - -for path in args.paths: - printEntries(getDirectoryEntries(path)) +# for path in args.paths: +# printEntries(getDirectoryEntries(path)) From 36c79a6e3793977222b9166e233e0ba92a508fd1 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 00:35:02 +0100 Subject: [PATCH 11/20] print multiple files and folders --- implement-shell-tools/ls/ls.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index 1afba00d5..0a614e6cf 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -31,15 +31,32 @@ def get_dir_entries(path, aFlag=args.a): return entries + def print_entries(entries, onePerLineFlag = args.opt_one): if (onePerLineFlag): for entry in entries: print(entry) - else: + elif (len(entries) > 0): for i in range(len(entries)-1): print(f"{entries[i]}\t", end="") - print(entries[-1]) + print(f"{entries[-1]}") + + +def main(): + # file and directory paths are processed separately + file_args = [arg for arg in args.paths if os.path.isfile(arg)] + dir_args = [arg for arg in args.paths if os.path.isdir(arg)] + + if (len(file_args) > 0): + print_entries(file_args) + + for index, path in enumerate(dir_args, start=0): + if (len(args.paths) > 1): + if (index > 0 or len(file_args) > 0): + print("") + print(f"{path}:") + print_entries(get_dir_entries(path)) -# for path in args.paths: -# printEntries(getDirectoryEntries(path)) +if __name__ == "__main__": + main() \ No newline at end of file From 34e629a847a706d8a48949655a8f95e0d0780d9e Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 00:52:57 +0100 Subject: [PATCH 12/20] add very basic error handling all paths that are not accessible as a file/folder throw same error --- implement-shell-tools/ls/ls.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index 0a614e6cf..d1dab65ad 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -44,8 +44,26 @@ def print_entries(entries, onePerLineFlag = args.opt_one): def main(): # file and directory paths are processed separately - file_args = [arg for arg in args.paths if os.path.isfile(arg)] - dir_args = [arg for arg in args.paths if os.path.isdir(arg)] + # file_args = [arg for arg in args.paths if os.path.isfile(arg)] + # dir_args = [arg for arg in args.paths if os.path.isdir(arg)] + + file_args = [] + dir_args = [] + invalid_args = [] + + # this is a simplication, it groups all errors under "invalid file" + # real ls would have different messages things like permission denied + # also bad because it makes two syscalls + for arg in args.paths: + if (os.path.isfile(arg)): + file_args.append(arg) + elif (os.path.isdir(arg)): + dir_args.append(arg) + else: + invalid_args.append(arg) + + for arg in invalid_args: + print(f"ls: {arg}: No such file or directory", file=sys.stderr) if (len(file_args) > 0): print_entries(file_args) From 2d9a74081bdb5e4d54c28fa95244850a4b4d1615 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 12:01:38 +0100 Subject: [PATCH 13/20] add a requirements file for argparse import --- implement-shell-tools/ls/requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 implement-shell-tools/ls/requirements.txt diff --git a/implement-shell-tools/ls/requirements.txt b/implement-shell-tools/ls/requirements.txt new file mode 100644 index 000000000..e9c6824d6 --- /dev/null +++ b/implement-shell-tools/ls/requirements.txt @@ -0,0 +1 @@ +argparse \ No newline at end of file From ef611c61a9569bbd465e264599f819febd78d134 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 12:29:04 +0100 Subject: [PATCH 14/20] parser setup --- implement-shell-tools/wc/wc.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 implement-shell-tools/wc/wc.py diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 000000000..de9f9c946 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,15 @@ +import argparse +import sys +import os + +parser = argparse.ArgumentParser( + prog="a simple version of wc. Takes in one or more files.", + description="ls command line tool which can accept -l -w -c cflags") + +parser.add_argument("-l", action="store_true", help="show line count") +parser.add_argument("-w", action="store_true", help="show word count") +parser.add_argument("-c", action="store_true", help="show byte count") + +parser.add_argument("paths", nargs="*", help="file(s) for which to show data") + +args = parser.parse_args() \ No newline at end of file From 575f0bf1f9ff92e3cc52be7cd43521474824e730 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 14:23:48 +0100 Subject: [PATCH 15/20] logic completed --- implement-shell-tools/wc/wc.py | 67 ++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index de9f9c946..d5aec9e01 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -6,10 +6,69 @@ prog="a simple version of wc. Takes in one or more files.", description="ls command line tool which can accept -l -w -c cflags") -parser.add_argument("-l", action="store_true", help="show line count") -parser.add_argument("-w", action="store_true", help="show word count") -parser.add_argument("-c", action="store_true", help="show byte count") +parser.add_argument("-l", action="store_true", help="show line count", default="l") +parser.add_argument("-w", action="store_true", help="show word count", default='w') +parser.add_argument("-c", action="store_true", help="show byte count", default="c") parser.add_argument("paths", nargs="*", help="file(s) for which to show data") -args = parser.parse_args() \ No newline at end of file +args = parser.parse_args() + +totals = {"l": 0, "w": 0, "c": 0} + +file_count = 0 + +for path in args.paths: + try: + if (os.path.isdir(path)): + print(f"wc: {path}: read: Is a directory") + except: + print(f"wc: {path} open: No such file or directory", file=sys.stderr) + file_count += 1 + continue + + if (os.path.isfile(path)): + file_count += 1 + output_str = "" + + with open(path, "r", encoding="utf-8") as file: + lines = file.readlines() + + if (args.l): + if (len(lines) > 0 and lines[-1] == ""): + lines.pop() + + line_count = len(lines) + totals["l"] += line_count + output_str += f"\t{line_count}" + + if (args.w): + word_count = 0 + for line in lines: + # python string.split splits on any white space + word_count += len(line.split()) + totals["w"] += word_count + output_str += f"\t{word_count}" + + if (args.c): + bytes = os.path.getsize(path) + totals["c"] += bytes + output_str += f"\t{bytes}" + + output_str += f" {path}" + print(output_str) + +if (file_count > 1): + res = {key : val for key, val in totals.items() + if val != 0} + total_str = "" + for v in res.values(): + total_str += f"\t{v}" + + total_str += " total" + print(total_str) + + + + + From 87d8179e9a98ffd31ac49397206b41c8ec2c243c Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 14:37:09 +0100 Subject: [PATCH 16/20] wc formatting matches real formatting exactly. --- implement-shell-tools/wc/wc.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index d5aec9e01..3d0c24dcf 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -2,6 +2,8 @@ import sys import os +# TODO: decompose into functions to make it more modular and reusable + parser = argparse.ArgumentParser( prog="a simple version of wc. Takes in one or more files.", description="ls command line tool which can accept -l -w -c cflags") @@ -40,7 +42,7 @@ line_count = len(lines) totals["l"] += line_count - output_str += f"\t{line_count}" + output_str += f"{line_count:8}" if (args.w): word_count = 0 @@ -48,12 +50,12 @@ # python string.split splits on any white space word_count += len(line.split()) totals["w"] += word_count - output_str += f"\t{word_count}" + output_str += f"{word_count:8}" if (args.c): bytes = os.path.getsize(path) totals["c"] += bytes - output_str += f"\t{bytes}" + output_str += f"{bytes:8}" output_str += f" {path}" print(output_str) @@ -63,7 +65,7 @@ if val != 0} total_str = "" for v in res.values(): - total_str += f"\t{v}" + total_str += f"{v:8}" total_str += " total" print(total_str) From 4995bfc099ef9a02822cc7dba5e0fb1bcced4116 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 14:38:48 +0100 Subject: [PATCH 17/20] add utf-8 encoding to cat --- implement-shell-tools/cat/cat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 57ae39682..16fb42367 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -16,7 +16,7 @@ def read_file(path): """Returns (content, error_message). error_message is None on success""" try: - with open(path, "r",) as f: + with open(path, "r", encoding="utf-8") as f: return f.read(), None except FileNotFoundError: return None, f"cat: {path}: No such file or directory" From 0e5e2ee7b7d84663aaf4bc531def6ad71a8340d3 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 14:47:31 +0100 Subject: [PATCH 18/20] no need to keep track of file count --- implement-shell-tools/wc/wc.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index 3d0c24dcf..d31796cda 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -18,8 +18,6 @@ totals = {"l": 0, "w": 0, "c": 0} -file_count = 0 - for path in args.paths: try: if (os.path.isdir(path)): @@ -30,7 +28,6 @@ continue if (os.path.isfile(path)): - file_count += 1 output_str = "" with open(path, "r", encoding="utf-8") as file: @@ -60,7 +57,7 @@ output_str += f" {path}" print(output_str) -if (file_count > 1): +if (len(args.paths) > 1): res = {key : val for key, val in totals.items() if val != 0} total_str = "" From 1ea4d3e37bb2b020c35d9f0ff1ab65a63360cc12 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Wed, 5 Aug 2026 15:00:50 +0100 Subject: [PATCH 19/20] correctly print error message --- implement-shell-tools/wc/wc.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index d31796cda..b6ce983a3 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -19,17 +19,12 @@ totals = {"l": 0, "w": 0, "c": 0} for path in args.paths: - try: - if (os.path.isdir(path)): - print(f"wc: {path}: read: Is a directory") - except: - print(f"wc: {path} open: No such file or directory", file=sys.stderr) - file_count += 1 - continue - - if (os.path.isfile(path)): + if (not os.path.exists(path)): + print(f"wc: {path}: open: No such file or directory", file=sys.stderr) + elif (os.path.isdir(path)): + print(f"wc: {path}: read: Is a directory") + elif (os.path.isfile(path)): output_str = "" - with open(path, "r", encoding="utf-8") as file: lines = file.readlines() From 085a1ac4945038adb4961006a0d6ea1a4f14ab22 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 6 Aug 2026 11:44:22 +0100 Subject: [PATCH 20/20] implement changes from code review --- implement-shell-tools/ls/requirements.txt | 1 - implement-shell-tools/wc/requirements.txt | 1 - implement-shell-tools/wc/wc.py | 27 +++++++++++++---------- 3 files changed, 15 insertions(+), 14 deletions(-) delete mode 100644 implement-shell-tools/ls/requirements.txt delete mode 100644 implement-shell-tools/wc/requirements.txt diff --git a/implement-shell-tools/ls/requirements.txt b/implement-shell-tools/ls/requirements.txt deleted file mode 100644 index e9c6824d6..000000000 --- a/implement-shell-tools/ls/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -argparse \ No newline at end of file diff --git a/implement-shell-tools/wc/requirements.txt b/implement-shell-tools/wc/requirements.txt deleted file mode 100644 index e9c6824d6..000000000 --- a/implement-shell-tools/wc/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -argparse \ No newline at end of file diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index b6ce983a3..43dec21df 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -8,9 +8,9 @@ prog="a simple version of wc. Takes in one or more files.", description="ls command line tool which can accept -l -w -c cflags") -parser.add_argument("-l", action="store_true", help="show line count", default="l") -parser.add_argument("-w", action="store_true", help="show word count", default='w') -parser.add_argument("-c", action="store_true", help="show byte count", default="c") +parser.add_argument("-l", action="store_true", help="show line count") +parser.add_argument("-w", action="store_true", help="show word count") +parser.add_argument("-c", action="store_true", help="show byte count") parser.add_argument("paths", nargs="*", help="file(s) for which to show data") @@ -18,29 +18,32 @@ totals = {"l": 0, "w": 0, "c": 0} +# if no flags then set all flags to true, same as in real wc +if (not args.l and not args.w and not args.c): + args.l = args.w = args.c = True + for path in args.paths: if (not os.path.exists(path)): print(f"wc: {path}: open: No such file or directory", file=sys.stderr) elif (os.path.isdir(path)): - print(f"wc: {path}: read: Is a directory") + print(f"wc: {path}: read: Is a directory", file=sys.stderr) elif (os.path.isfile(path)): output_str = "" with open(path, "r", encoding="utf-8") as file: - lines = file.readlines() + content = file.read() + lines = content.split('\n') + if (args.l): - if (len(lines) > 0 and lines[-1] == ""): - lines.pop() + #if (len(lines)) > 0: + # lines[-1].strip() - line_count = len(lines) + line_count = content.count('\n') totals["l"] += line_count output_str += f"{line_count:8}" if (args.w): - word_count = 0 - for line in lines: - # python string.split splits on any white space - word_count += len(line.split()) + word_count = len(content.split()) totals["w"] += word_count output_str += f"{word_count:8}"