From 5b484f155ebca85398d4005ff8341c5a0029f337 Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Fri, 1 Aug 2025 22:47:42 +0100 Subject: [PATCH 1/2] Completed implementing shell tools in python. --- implement-shell-tools/cat/my_cat.py | 52 +++++++++++++++++++ implement-shell-tools/ls/my_ls.py | 53 ++++++++++++++++++++ implement-shell-tools/wc/my_wc.py | 78 +++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 implement-shell-tools/cat/my_cat.py create mode 100644 implement-shell-tools/ls/my_ls.py create mode 100644 implement-shell-tools/wc/my_wc.py diff --git a/implement-shell-tools/cat/my_cat.py b/implement-shell-tools/cat/my_cat.py new file mode 100644 index 00000000..f19fc2b2 --- /dev/null +++ b/implement-shell-tools/cat/my_cat.py @@ -0,0 +1,52 @@ +import argparse +import sys + +parser = argparse.ArgumentParser( + description="Reads and prints one or more files, optionally numbering lines continuously" +) +parser.add_argument("paths", nargs='+', help="One or more file paths") +parser.add_argument("-n", "--number", action="store_true", help="Number all output lines") +parser.add_argument("-b", "--number-nonblank", action="store_true", help="Number non-empty output lines") + +args = parser.parse_args() + +file_paths = args.paths +number_nonblank = args.number_nonblank +number_all = args.number and not number_nonblank + +line_number = 1 + +for path in file_paths: + try: + with open(path, 'r', encoding='utf-8') as f: + lines = f.readlines() + + if number_nonblank: + nonblank_lines = [line for line in lines if line.strip()] + max_digits = len(str(len(nonblank_lines))) + + for line in lines: + if line.strip() == "": + print() + else: + num_str = str(line_number).rjust(max_digits) + print(f"{num_str}\t{line.rstrip()}") + line_number += 1 + + elif number_all: + max_digits = len(str(len(lines) * len(file_paths))) + + for line in lines: + num_str = str(line_number).rjust(max_digits) + print(f"{num_str}\t{line.rstrip()}") + line_number += 1 + + else: + for line in lines: + print(line, end='') + if not lines[-1].endswith('\n'): + print() + + except Exception as e: + print(f'Error reading file "{path}": {e}', file=sys.stderr) + sys.exit(1) diff --git a/implement-shell-tools/ls/my_ls.py b/implement-shell-tools/ls/my_ls.py new file mode 100644 index 00000000..189d05e8 --- /dev/null +++ b/implement-shell-tools/ls/my_ls.py @@ -0,0 +1,53 @@ +import os +import sys +import stat +import argparse + +parser = argparse.ArgumentParser( + description="List files in a directory (simplified ls implementation)" +) +parser.add_argument("paths", nargs="*", default=["."], help="One or more file or directory paths") +parser.add_argument("-l", "--longList", action="store_true", help="Long listing format") +parser.add_argument("-a", "--all", action="store_true", help="Include hidden files") + +args = parser.parse_args() + +file_paths = args.paths +show_long = args.longList +show_all = args.all + +def format_permissions(mode): + return stat.filemode(mode) + +for input_path in file_paths: + try: + if not os.path.exists(input_path): + raise FileNotFoundError(f'No such file or directory: {input_path}') + + if os.path.isfile(input_path): + if show_long: + file_stat = os.stat(input_path) + perms = format_permissions(file_stat.st_mode) + size = str(file_stat.st_size).rjust(6) + print(f"{perms} {size} {input_path}") + else: + print(input_path) + + elif os.path.isdir(input_path): + entries = os.listdir(input_path) + if not show_all: + entries = [e for e in entries if not e.startswith(".")] + + for entry in entries: + full_path = os.path.join(input_path, entry) + entry_stat = os.stat(full_path) + if show_long: + perms = format_permissions(entry_stat.st_mode) + size = str(entry_stat.st_size).rjust(6) + print(f"{perms} {size} {entry}") + else: + print(entry) + + except Exception as e: + print(f'Error reading "{input_path}": {e}', file=sys.stderr) + sys.exit(1) diff --git a/implement-shell-tools/wc/my_wc.py b/implement-shell-tools/wc/my_wc.py new file mode 100644 index 00000000..ffb6fb75 --- /dev/null +++ b/implement-shell-tools/wc/my_wc.py @@ -0,0 +1,78 @@ +import os +import sys +import argparse + +# CLI argument parsing +parser = argparse.ArgumentParser(description="Simplified implementation of wc") +parser.add_argument("paths", nargs="*", default=["."], help="One or more file or directory paths") +parser.add_argument("-l", "--line", action="store_true", help="Count lines") +parser.add_argument("-w", "--word", action="store_true", help="Count words") +parser.add_argument("-c", "--character", action="store_true", help="Count characters") + +args = parser.parse_args() +file_paths = args.paths + +# Fallback: if no options passed, show all +show_line = args.line +show_word = args.word +show_char = args.character +show_all = not (show_line or show_word or show_char) + +# Count content in a string +def count_content(content): + lines = content.splitlines() + words = content.strip().split() + characters = len(content) + return len(lines), len(words), characters + +# Totals for multiple files +total = { + "lines": 0, + "words": 0, + "characters": 0 +} + +file_count = 0 + +for input_path in file_paths: + try: + if os.path.isdir(input_path): + print(f"{input_path} is a directory. Skipping.") + continue + + with open(input_path, "r", encoding="utf-8") as f: + content = f.read() + + lines, words, characters = count_content(content) + + total["lines"] += lines + total["words"] += words + total["characters"] += characters + file_count += 1 + + # Prepare output per file + output_parts = [] + if show_line or show_all: + output_parts.append(f"{lines:8}") + if show_word or show_all: + output_parts.append(f"{words:8}") + if show_char or show_all: + output_parts.append(f"{characters:8}") + + output_parts.append(input_path) + print(" ".join(output_parts)) + + except Exception as e: + print(f'Error reading "{input_path}": {e}', file=sys.stderr) + +# Print totals if more than one file processed +if file_count > 1: + output_parts = [] + if show_line or show_all: + output_parts.append(f"{total['lines']:8}") + if show_word or show_all: + output_parts.append(f"{total['words']:8}") + if show_char or show_all: + output_parts.append(f"{total['characters']:8}") + output_parts.append("total") + print(" ".join(output_parts)) From a18c426cf9a02b0b5a1d576a05ae89870ab93d69 Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Tue, 5 Aug 2025 09:24:43 +0100 Subject: [PATCH 2/2] added .venv files to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3c3629e6..3e5cc695 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +.venv/ \ No newline at end of file