From ac1aaf78c12e70d01f4259f89235013ba4f890ec Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 28 Nov 2025 10:29:24 +0200 Subject: [PATCH 01/13] New files added for each command --- implement-shell-tools/cat/cat.py | 0 implement-shell-tools/ls/ls.py | 0 implement-shell-tools/wc/wc.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 implement-shell-tools/cat/cat.py create mode 100644 implement-shell-tools/ls/ls.py create mode 100644 implement-shell-tools/wc/wc.py diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 000000000..e69de29bb diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 000000000..e69de29bb diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 000000000..e69de29bb From 06abf21c2070e19d66cb5b6cca5a751d368166e6 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 28 Nov 2025 13:11:19 +0200 Subject: [PATCH 02/13] Project set up --- implement-shell-tools/cat/cat.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index e69de29bb..89c58bd34 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,14 @@ +import argparse + +parser = argparse.ArgumentParser( + prog="py-cat", + description="A Python implementation of the Unix cat command", +) + +parser.add_argument("-n", "--number", help="Number all output lines") +parser.add_argument("-b", "--numberNonBlank", help="Numbers only non-empty lines. Overrides -n option") +parser.add_argument("path", nargs="+", help="The file path to process") + +args = parser.parse_args() + + \ No newline at end of file From d9799f8687eca95f30410b666209e63f0408395f Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 28 Nov 2025 13:17:18 +0200 Subject: [PATCH 03/13] Basic cat command implemented --- implement-shell-tools/cat/cat.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 89c58bd34..43bf87003 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -11,4 +11,15 @@ args = parser.parse_args() - \ No newline at end of file +# Read and concatenate file contents +content = "" + +for path in args.path: + with open(path, "r") as f: + content += f.read() + +# Remove trailing newline if present +if content.endswith("\n"): + content = content[:-1] + +print(content) \ No newline at end of file From 621a896d606d38a2ae3014258277c30f0f43d9c4 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Sat, 29 Nov 2025 11:25:18 +0200 Subject: [PATCH 04/13] -n and -b implemented --- implement-shell-tools/cat/cat.py | 36 +++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 43bf87003..59087bf60 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -5,21 +5,47 @@ description="A Python implementation of the Unix cat command", ) -parser.add_argument("-n", "--number", help="Number all output lines") -parser.add_argument("-b", "--numberNonBlank", help="Numbers only non-empty lines. Overrides -n option") +parser.add_argument("-n", "--number", action="store_true", help="Number all output lines") +parser.add_argument("-b", "--numberNonBlank", action="store_true", help="Numbers only non-empty lines. Overrides -n option") parser.add_argument("path", nargs="+", help="The file path to process") args = parser.parse_args() +def number_all(lines): + numbered = [] + for i, line in enumerate(lines): + numbered.append(f"{i + 1:>6}\t{line}") + return numbered + +def number_non_blank(lines): + numbered = [] + counter = 1 + for line in lines: + if line == "": + numbered.append(line) + else: + numbered.append(f"{counter:>6}\t{line}") + counter += 1 + return numbered + # Read and concatenate file contents content = "" for path in args.path: with open(path, "r") as f: content += f.read() - -# Remove trailing newline if present + if content.endswith("\n"): content = content[:-1] -print(content) \ No newline at end of file +# Split content into lines +lines = content.split("\n") + +# Output logic + +if args.numberNonBlank: + print("\n".join(number_non_blank(lines))) +elif args.number: + print("\n".join(number_all(lines))) +else: + print("\n".join(lines)) \ No newline at end of file From 5f62f757cc75637273af75c332f62c6b7097c4f5 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Sun, 30 Nov 2025 20:03:42 +0200 Subject: [PATCH 05/13] gitignore updated --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3c3629e64..5da303acc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +**/.venv +**/requirements.txt From d843352e79758cdb70279c07c8a11619c48c5a24 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Sun, 30 Nov 2025 21:01:02 +0200 Subject: [PATCH 06/13] argparse information added to ls.py --- implement-shell-tools/ls/ls.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index e69de29bb..9df442a40 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,13 @@ +import argparse + +parser = argparse.ArgumentParser( + prog="py-ls", + description="A Python implementation of the Unix ls command", +) + +parser.add_argument("-1", action="store_true", help="List one file per line") +parser.add_argument("-a", "--all", action="store_true", help="Include directory entries whose names begin with a dot (.)") +parser.add_argument("path", nargs="+", help="The file path to process") + +args = parser.parse_args() + From 8ee7c0eefb75265b78e4256dd146f70b64d66d1e Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 1 Dec 2025 09:07:31 +0200 Subject: [PATCH 07/13] ls command implemented --- implement-shell-tools/ls/ls.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index 9df442a40..f8e0f9102 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -1,13 +1,25 @@ import argparse +import os parser = argparse.ArgumentParser( prog="py-ls", description="A Python implementation of the Unix ls command", ) -parser.add_argument("-1", action="store_true", help="List one file per line") -parser.add_argument("-a", "--all", action="store_true", help="Include directory entries whose names begin with a dot (.)") -parser.add_argument("path", nargs="+", help="The file path to process") +parser.add_argument("-1", dest="_1", action="store_true", help="List one file per line") +parser.add_argument("-a", "--all", action="store_true", help="Include entries that begin with a dot (.)") +parser.add_argument("path", nargs="?", default=".", help="Directory to list") args = parser.parse_args() +files = os.listdir(args.path) + +if args.all: + files = [".", ".."] + files +else: + files = [file for file in files if not file.startswith(".")] + +if args._1: + print("\n".join(files)) +else: + print(" ".join(files)) From 7d85fc5f1c2331d59f79825288055a7c0d85e47f Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 1 Dec 2025 09:18:03 +0200 Subject: [PATCH 08/13] wc argparse info loaded --- implement-shell-tools/wc/wc.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index e69de29bb..9c6138d53 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,14 @@ +import argparse + +parser = argparse.ArgumentParser( + prog="py-wc", + description="A Python implementation of the Unix wc command", +) + +parser.add_argument("-l", "--lines", help="Print the newline counts") +parser.add_argument("-w", "--words", action="store_true", help="Print the word counts") +parser.add_argument("-c", "--bytes", action="store_true", help="Print the byte counts") +parser.add_argument("path", nargs="?", default=".", help="Directory to list") + +args = parser.parse_args() + From 95b2ec835ae300e1a8cc5919ace86ea2bd48eb2e Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 1 Dec 2025 22:37:46 +0200 Subject: [PATCH 09/13] logic for file details complete --- implement-shell-tools/wc/wc.py | 39 ++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index 9c6138d53..76b935e87 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -1,14 +1,49 @@ import argparse +import os parser = argparse.ArgumentParser( prog="py-wc", description="A Python implementation of the Unix wc command", ) -parser.add_argument("-l", "--lines", help="Print the newline counts") +parser.add_argument("-l", "--lines", action="store_true", help="Print the newline counts") parser.add_argument("-w", "--words", action="store_true", help="Print the word counts") parser.add_argument("-c", "--bytes", action="store_true", help="Print the byte counts") -parser.add_argument("path", nargs="?", default=".", help="Directory to list") +parser.add_argument("path", nargs="+", help="The file path to process") args = parser.parse_args() +file_paths = args.path + +file_details_list = [] + +# Get file details +for file_path in file_paths: + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + + details = { + "file_path": file_path, + "file_size": os.path.getsize(file_path), + "line_count": content.count("\n"), # matches real wc -l + "word_count": len(content.split()), # split on whitespace + } + + file_details_list.append(details) + + +print(file_details_list) + +def get_line_count(text): + return text.count("\n") + + +def get_word_count(text): + return len(text.split()) + + + +show_all = not (args.lines or args.words or args.bytes) + + +# print(args) From d8dc9a704e564ca6ae859d55ed65496c5fcc90a2 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 1 Dec 2025 23:05:11 +0200 Subject: [PATCH 10/13] Logic for totals completed. Totals saved as an object just like the file details so we can format the output easily --- implement-shell-tools/wc/wc.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index 76b935e87..0b2ad5f3f 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -15,6 +15,9 @@ file_paths = args.path file_details_list = [] +line_count_total = 0 +word_count_total = 0 +file_size_total = 0 # Get file details for file_path in file_paths: @@ -22,25 +25,28 @@ content = f.read() details = { - "file_path": file_path, - "file_size": os.path.getsize(file_path), "line_count": content.count("\n"), # matches real wc -l "word_count": len(content.split()), # split on whitespace + "file_size": os.path.getsize(file_path), + "file_path": file_path, } + # Update totals + line_count_total += details["line_count"] + word_count_total += details["word_count"] + file_size_total += details["file_size"] + total_path = "Total" file_details_list.append(details) - -print(file_details_list) - -def get_line_count(text): - return text.count("\n") - - -def get_word_count(text): - return len(text.split()) +totals_details = { + "line_count": line_count_total, + "word_count": word_count_total, + "file_size": file_size_total, + "file_path": "total" +} +print(totals_details) show_all = not (args.lines or args.words or args.bytes) From 66b014735b1ec338b42d502ee5f92d045d4043d4 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Wed, 3 Dec 2025 14:06:30 +0200 Subject: [PATCH 11/13] wc implemented and working for wc sample-files/* --- implement-shell-tools/wc/wc.py | 50 ++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index 0b2ad5f3f..d3f60cfa5 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -13,6 +13,45 @@ args = parser.parse_args() file_paths = args.path +lines_flag, words_flag, bytes_flag = args.lines, args.words, args.bytes + +def format_output(details_list, totals=None): + show_all = not (lines_flag or words_flag or bytes_flag) + output_lines = [] + + # Per-file output + for d in details_list: + line = "" + + if show_all or lines_flag: + line += f"{d['line_count']:>3} " + + if show_all or words_flag: + line += f"{d['word_count']:>3} " + + if show_all or bytes_flag: + line += f"{d['file_size']:>3} " + + line += d["file_path"] + output_lines.append(line) + + # Totals (only if more than one file) + if totals and len(details_list) > 1: + total_line = "" + + if show_all or lines_flag: + total_line += f"{totals['line_count']:>3} " + + if show_all or words_flag: + total_line += f"{totals['word_count']:>3} " + + if show_all or bytes_flag: + total_line += f"{totals['file_size']:>3} " + + total_line += "total" + output_lines.append(total_line) + + return "\n".join(output_lines) file_details_list = [] line_count_total = 0 @@ -25,8 +64,8 @@ content = f.read() details = { - "line_count": content.count("\n"), # matches real wc -l - "word_count": len(content.split()), # split on whitespace + "line_count": content.count("\n"), + "word_count": len(content.split()), "file_size": os.path.getsize(file_path), "file_path": file_path, } @@ -42,14 +81,13 @@ "line_count": line_count_total, "word_count": word_count_total, "file_size": file_size_total, - "file_path": "total" } +formatted_output = format_output(file_details_list, totals_details) +print(formatted_output) -print(totals_details) - -show_all = not (args.lines or args.words or args.bytes) # print(args) +# 1 4 20 sample-files/1.txt \ No newline at end of file From 58e58072a8787ef8da0e51bab416f918bfc304be Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Wed, 3 Dec 2025 14:21:32 +0200 Subject: [PATCH 12/13] Code cleaned up --- implement-shell-tools/wc/wc.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index d3f60cfa5..063978aa5 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -15,7 +15,8 @@ file_paths = args.path lines_flag, words_flag, bytes_flag = args.lines, args.words, args.bytes -def format_output(details_list, totals=None): +def format_output(details_list, totals=None, flags=None): + lines_flag, words_flag, bytes_flag = flags show_all = not (lines_flag or words_flag or bytes_flag) output_lines = [] @@ -43,7 +44,7 @@ def format_output(details_list, totals=None): total_line += f"{totals['line_count']:>3} " if show_all or words_flag: - total_line += f"{totals['word_count']:>3} " + total_line += f"{totals['word_count']:>3} " if show_all or bytes_flag: total_line += f"{totals['file_size']:>3} " @@ -53,12 +54,13 @@ def format_output(details_list, totals=None): return "\n".join(output_lines) + +# Collect file details file_details_list = [] line_count_total = 0 word_count_total = 0 file_size_total = 0 -# Get file details for file_path in file_paths: with open(file_path, "r", encoding="utf-8") as f: content = f.read() @@ -69,11 +71,10 @@ def format_output(details_list, totals=None): "file_size": os.path.getsize(file_path), "file_path": file_path, } - # Update totals + line_count_total += details["line_count"] word_count_total += details["word_count"] file_size_total += details["file_size"] - total_path = "Total" file_details_list.append(details) @@ -83,11 +84,6 @@ def format_output(details_list, totals=None): "file_size": file_size_total, } -formatted_output = format_output(file_details_list, totals_details) -print(formatted_output) - - - -# print(args) - -# 1 4 20 sample-files/1.txt \ No newline at end of file +# Final output +flags = (lines_flag, words_flag, bytes_flag) +print(format_output(file_details_list, totals_details, flags)) From 7df0722d5374316c7c1a5419743659cf503dc4cb Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 9 Jan 2026 15:38:34 +0200 Subject: [PATCH 13/13] **/requirements.txt removed from gitignore file --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5da303acc..3e5a98368 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules **/.venv -**/requirements.txt +