From 1bce56a1b1492cc805d1fce9fe56b531d25ca7ee Mon Sep 17 00:00:00 2001 From: ike-agu Date: Fri, 12 Dec 2025 01:15:57 +0000 Subject: [PATCH 1/6] Add python implementation of catCommand and .gitignore file --- implement-shell-tools/cat/README.md | 19 ++++++++++- implement-shell-tools/cat/cat.py | 50 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 implement-shell-tools/cat/cat.py diff --git a/implement-shell-tools/cat/README.md b/implement-shell-tools/cat/README.md index 7284a5e6..86291b40 100644 --- a/implement-shell-tools/cat/README.md +++ b/implement-shell-tools/cat/README.md @@ -1,4 +1,4 @@ -# Implement `cat` +# Implement `cat` in python You should already be familiar with the `cat` command line tool. @@ -15,3 +15,20 @@ It must act the same as `cat` would, if run from the directory containing this R Matching any additional behaviours or flags are optional stretch goals. We recommend you start off supporting no flags, then add support for `-n`, then add support for `-b`. + +*========commands for running the scripts======== + +* No flags +python cat.py sample-files/1.txt + +* Number every line (-n) +python cat.py -n sample-files/1.txt + +* Display all files in directory(shell expands *.txt) +python cat.py sample-files/*.txt + +* Number every line across multiple files(including empty ones) +python cat.py -n sample-files/*.txt + +* Number non-empty lines only (-b) +python cat.py -b sample-files/3.txt diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 00000000..96bbcdbc --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,50 @@ +import argparse +# ------------------------------------------- +# Set up the argument parser +# ------------------------------------------- + +parser = argparse.ArgumentParser( + prog ="display-file-content", + description = "Implement cat command with -n and -b flag support", + ) + +parser.add_argument("-n", "--number-all-lines", + action="store_true", + help="Number every line in the file" + ) + +parser.add_argument("-b", "--number-non-empty-lines", + action="store_true", + help="Number non empty lines in the file" + ) + +parser.add_argument("paths", nargs="+", help="File paths to process") + +args = parser.parse_args() + +# ------------------------------------------- +# Implement functionality +# ------------------------------------------- + +line_number = 1 + +for filepath in args.paths: + with open(filepath, "r", encoding="utf-8") as f: + content = f.read() + + lines = content.split("\n") + + for line in lines: + if args.number_all_lines: + print(f"{line_number} {line}") + line_number += 1 + + elif args.number_non_empty_lines: + if line.strip() == "": + print(line) + else: + print(f"{line_number} {line}") + line_number +=1 + + else: + print(line) From 4f141b32a5fc700f7dd7d0ddd74234f332b8ab0d Mon Sep 17 00:00:00 2001 From: ike-agu Date: Fri, 12 Dec 2025 02:28:56 +0000 Subject: [PATCH 2/6] Implement ls commands in python --- implement-shell-tools/ls/README.md | 5 ++ implement-shell-tools/ls/ls_py.py | 73 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100755 implement-shell-tools/ls/ls_py.py diff --git a/implement-shell-tools/ls/README.md b/implement-shell-tools/ls/README.md index edbfb811..2190f603 100644 --- a/implement-shell-tools/ls/README.md +++ b/implement-shell-tools/ls/README.md @@ -13,3 +13,8 @@ It must act the same as `ls` would, if run from the directory containing this RE Matching any additional behaviours or flags are optional stretch goals. We recommend you start off supporting just `-1`, then adding support for `-a`. + +*=======command for running the script=========== +python3 ls_py.py -1 +python3 ls_py.py -1 sample-files +python3 ls_py.py -1 -a sample-files diff --git a/implement-shell-tools/ls/ls_py.py b/implement-shell-tools/ls/ls_py.py new file mode 100755 index 00000000..82df9640 --- /dev/null +++ b/implement-shell-tools/ls/ls_py.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys + + +# Set up the argument parser +def parse_args(): + parser = argparse.ArgumentParser( + prog="list_files_in_directory", + description="Implement ls commands to list files in directory" + ) + + parser.add_argument( + "-1","--file-per-line", + action="store_true", + dest="file_per_line", + help="list files one per line" + ) + + parser.add_argument( + "-a", + "--files-and-hidden-ones", + action="store_true", + dest="files_and_hidden_ones", + help="list all files including hidden ones", + ) + + parser.add_argument( + "paths", + nargs="*", + help="directories to list" + ) + + return parser.parse_args() + +# if no paths, default to current directory +def get_paths(args): + if len(args.paths) == 0: + return ["."] + return args.paths + +# list a single directory +def list_directory(directory_path, show_hidden, file_per_line): + try: + entries = os.listdir(directory_path) + except OSError as err: + print(f"ls: cannot access '{directory_path}': {err}", file=sys.stderr) + return + + if not show_hidden: + entries = [name for name in entries if not name.startswith(".")] + + if file_per_line: + for name in entries: + print(name) + else: + print(" ".join(entries)) + +def main(): + args = parse_args() + paths = get_paths(args) + + for directory_path in paths: + list_directory( + directory_path=directory_path, + show_hidden=args.files_and_hidden_ones, + file_per_line=args.file_per_line, + ) + +if __name__ == "__main__": + main() From 7a738aeff4c430a56e856a3e70d40a14f4713576 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Fri, 12 Dec 2025 12:49:55 +0000 Subject: [PATCH 3/6] Implement wc command-line tool in python --- implement-shell-tools/wc/README.md | 19 +++++- implement-shell-tools/wc/wc.py | 93 ++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 implement-shell-tools/wc/wc.py diff --git a/implement-shell-tools/wc/README.md b/implement-shell-tools/wc/README.md index bd76b655..7261aca7 100644 --- a/implement-shell-tools/wc/README.md +++ b/implement-shell-tools/wc/README.md @@ -1,4 +1,4 @@ -# Implement `wc` +# Implement `wc` in python You should already be familiar with the `wc` command line tool. @@ -15,3 +15,20 @@ It must act the same as `wc` would, if run from the directory containing this RE Matching any additional behaviours or flags are optional stretch goals. We recommend you start off supporting no flags for one file, then add support for multiple files, then add support for the flags. + +*======Command for testing the script======== + +* All sample files +python wc.py sample-files/* + +* Just lines +python wc.py -l sample-files/3.txt + +* Just words +python wc.py -w sample-files/3.txt + +* Just characters +python wc.py -c sample-files/3.txt + +* Lines with multiple files (to see totals) +python wc.py -l sample-files/* diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 00000000..70d83d21 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,93 @@ +import argparse +import sys +import re + + +def main(): + # -------------------------------------------------------- + # 1. Set up argparse + # -------------------------------------------------------- + parser= argparse.ArgumentParser( + prog="wc", + description ="wc command implementation in python" + ) + + parser.add_argument("-l", action="store_true", help ="show number of lines only") + parser.add_argument("-w", action="store_true", help="show number of words only") + parser.add_argument("-c", action="store_true", help="show number of characters only") + + parser.add_argument("paths", nargs="*", help="file paths to process") + + args = parser.parse_args() + + # -------------------------------------------------------- + # 2. Ensures at least one path exists + # -------------------------------------------------------- + if len(args.paths) == 0: + print("wc: no file specified", file=sys.stderr) + sys.exit(1) + + totals= {"lines": 0, "words": 0, "chars": 0} + + # -------------------------------------------------------- + # 3. Loop over each file path and process it + # -------------------------------------------------------- + for file_path in args.paths: + try: + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + except OSError as err: + print(f"wc: cannot read file'{file_path}': {err}", file=sys.stderr) + continue + + # -------------------------------------------------------- + # 4. Count values + # -------------------------------------------------------- + line_count = len(content.split("\n")) + + words = [w for w in re.split(r"\s+", content) if w] + word_count = len(words) + + char_count = len(content) + + totals["lines"] += line_count + totals["words"] += word_count + totals["chars"] +=char_count + + # -------------------------------------------------------- + # 5. Decide what to print based on flags + # -------------------------------------------------------- + no_flags = not args.l and not args.w and not args.c + + if no_flags: + print(f"{line_count} {word_count} {char_count} {file_path}") + continue + + if args.l: + print(f"{line_count} {file_path}" ) + + if args.w: + print(f"{word_count} {file_path}") + + if args.c: + print(f"{char_count} {file_path}") + + + # -------------------------------------------------------- + # 6. Print totals if there are multiple files + # -------------------------------------------------------- + if len(args.paths) > 1: + no_flags = not args.l and not args.w and not args.c + + if no_flags: + print(f"{totals['lines']} {totals['words']} {totals['chars']} total") + + if args.l: + print(f"{totals['lines']} total") + if args.w: + print(f"{totals['words']} total") + if args.c: + print(f"{totals['chars']} total") + +if __name__ == "__main__": + main() From da1d1cc152cdaab823722b22b82d0d0e7f558edb Mon Sep 17 00:00:00 2001 From: ike-agu Date: Fri, 12 Dec 2025 12:52:53 +0000 Subject: [PATCH 4/6] Fix comments --- implement-shell-tools/wc/wc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index 70d83d21..2420f5d3 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -5,7 +5,7 @@ def main(): # -------------------------------------------------------- - # 1. Set up argparse + # Set up argparse # -------------------------------------------------------- parser= argparse.ArgumentParser( prog="wc", @@ -21,7 +21,7 @@ def main(): args = parser.parse_args() # -------------------------------------------------------- - # 2. Ensures at least one path exists + # Ensures at least one path exists # -------------------------------------------------------- if len(args.paths) == 0: print("wc: no file specified", file=sys.stderr) @@ -30,7 +30,7 @@ def main(): totals= {"lines": 0, "words": 0, "chars": 0} # -------------------------------------------------------- - # 3. Loop over each file path and process it + # Loop over each file path and process it # -------------------------------------------------------- for file_path in args.paths: try: @@ -41,7 +41,7 @@ def main(): continue # -------------------------------------------------------- - # 4. Count values + # Count values # -------------------------------------------------------- line_count = len(content.split("\n")) @@ -55,7 +55,7 @@ def main(): totals["chars"] +=char_count # -------------------------------------------------------- - # 5. Decide what to print based on flags + # Decide what to print based on flags # -------------------------------------------------------- no_flags = not args.l and not args.w and not args.c @@ -74,7 +74,7 @@ def main(): # -------------------------------------------------------- - # 6. Print totals if there are multiple files + # Print totals if there are multiple files # -------------------------------------------------------- if len(args.paths) > 1: no_flags = not args.l and not args.w and not args.c From 0a8482238652c9c003db2b81dc5d5036fd213dd9 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Wed, 21 Jan 2026 18:31:11 +0000 Subject: [PATCH 5/6] Fix line numbering to match cat -n and -b behaviour --- implement-shell-tools/cat/cat.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 96bbcdbc..d63825a4 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -30,21 +30,17 @@ for filepath in args.paths: with open(filepath, "r", encoding="utf-8") as f: - content = f.read() - - lines = content.split("\n") - - for line in lines: - if args.number_all_lines: - print(f"{line_number} {line}") - line_number += 1 - - elif args.number_non_empty_lines: + for line in f: + if args.number_non_empty_lines: if line.strip() == "": - print(line) + print(line, end="") else: - print(f"{line_number} {line}") + print(f"{line_number} {line}", end="") + line_number += 1 + + elif args.number_all_lines: + print(f"{line_number} {line}", end="") line_number +=1 else: - print(line) + print(line, end="") From 4471a05a0bc6a5b80177d9e329e867d35108f0cf Mon Sep 17 00:00:00 2001 From: ike-agu Date: Wed, 21 Jan 2026 19:36:54 +0000 Subject: [PATCH 6/6] Refactor cat implementationinto functions for readability --- implement-shell-tools/cat/cat.py | 87 +++++++++++++++++++------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index d63825a4..46c0ed6d 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -1,46 +1,63 @@ import argparse -# ------------------------------------------- -# Set up the argument parser -# ------------------------------------------- -parser = argparse.ArgumentParser( - prog ="display-file-content", - description = "Implement cat command with -n and -b flag support", - ) +def parse_args(): + parser = argparse.ArgumentParser( + prog ="display-file-content", + description = "Implement cat command with -n and -b flag support", + ) -parser.add_argument("-n", "--number-all-lines", - action="store_true", - help="Number every line in the file" - ) + parser.add_argument("-n", "--number-all-lines", + action="store_true", + help="Number every line in the file" + ) -parser.add_argument("-b", "--number-non-empty-lines", - action="store_true", - help="Number non empty lines in the file" - ) + parser.add_argument("-b", "--number-non-empty-lines", + action="store_true", + help="Number non empty lines in the file" + ) -parser.add_argument("paths", nargs="+", help="File paths to process") + parser.add_argument("paths", nargs="+", help="File paths to process") -args = parser.parse_args() + args = parser.parse_args() + return args -# ------------------------------------------- -# Implement functionality -# ------------------------------------------- -line_number = 1 +def print_line(line, line_number = None): + if line_number is None: + print(line, end="") + else: + print(f"{line_number} {line}", end="") -for filepath in args.paths: +def process_line(line, args, line_number): + if args.number_non_empty_lines: + if line.strip() == "": + print_line(line) + else: + print_line(line, line_number) + line_number += 1 + + elif args.number_all_lines: + print_line(line, line_number) + line_number +=1 + + else: + print_line(line) + + return line_number + +def process_file(filepath, args, line_number): + """Read a file and print its lines. Returns updated line_number.""" with open(filepath, "r", encoding="utf-8") as f: for line in f: - if args.number_non_empty_lines: - if line.strip() == "": - print(line, end="") - else: - print(f"{line_number} {line}", end="") - line_number += 1 - - elif args.number_all_lines: - print(f"{line_number} {line}", end="") - line_number +=1 - - else: - print(line, end="") + line_number = process_line(line, args, line_number) + return line_number + +def main(): + args = parse_args() + line_number = 1 + + for filepath in args.paths: + line_number = process_file(filepath, args, line_number) + +if __name__ == "__main__": + main()