From 7a1d23c6fc78dfc940284f3cc5fb1da727981f67 Mon Sep 17 00:00:00 2001 From: mikiyas-stp Date: Tue, 12 Aug 2025 00:04:48 +0100 Subject: [PATCH 1/9] ls implementation --- implement-shell-tools/ls/ls.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 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 00000000..ccc7a0bc --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,26 @@ +import argparse #for parsing command-line arguments and options +import os #to interact with the os for listing files and handling paths +import sys #Allows interaction with the Py runtime env like reading/writing output,exiting. + + +def list_directory(directory, show_all, one_per_line): + try: + files = os.listdir(directory) + except Exception as e: + print(f"ls: cannot access '{directory}': {e}", file=sys.stderr) + return 1 + + +def main(): + parser = argparse.ArgumentParser(description="A simplified ls implementation.") + parser.add_argument('-1', dest='one_per_line', action='store_true', help='list one file per line') + parser.add_argument('-a', action='store_true', help='include hidden files') + parser.add_argument('directory', nargs='?', default='.', help='directory to list (default: current directory)') + + args = parser.parse_args() + + exit_code = list_directory(args.directory, args.a, args.one_per_line) + sys.exit(exit_code) + +if __name__ == "__main__": + main() From c3067f85866d554eaf69fa25c324350a5eba2f0c Mon Sep 17 00:00:00 2001 From: mikiyas-stp Date: Tue, 12 Aug 2025 00:05:11 +0100 Subject: [PATCH 2/9] conditions for ls implementation --- implement-shell-tools/ls/ls.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index ccc7a0bc..ea16010a 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -10,6 +10,17 @@ def list_directory(directory, show_all, one_per_line): print(f"ls: cannot access '{directory}': {e}", file=sys.stderr) return 1 + if not show_all: + files = [f for f in files if not f.startswith('.')] + files.sort() + + if one_per_line: + for file in files: + print(file) + else: + print(' '.join(files)) + + return 0 def main(): parser = argparse.ArgumentParser(description="A simplified ls implementation.") From 851444c1f03b205b5207e35b3b2c32c854fa6d9d Mon Sep 17 00:00:00 2001 From: mikiyas-stp Date: Tue, 12 Aug 2025 00:06:20 +0100 Subject: [PATCH 3/9] cat implementation using python --- implement-shell-tools/cat/cat.py | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 implement-shell-tools/cat/cat.py diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 00000000..1c9e32ef --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,37 @@ +import argparse +import sys + +def main(): + parser = argparse.ArgumentParser( + prog="display-content-of-a-file", + description="cat is used to display the content of a file or print the content of a file." + ) + parser.add_argument('-n', action='store_true', help='number output lines') + parser.add_argument('-b', action='store_true', help='number non-empty output lines') + parser.add_argument('paths', nargs='+', help="The file path(s) to process") + + options = parser.parse_args() + + combined_data = "" + for path in options.paths: + try: + with open(path, 'r', encoding='utf-8') as f: + combined_data += f.read() + "\n" + except Exception as e: + print(f"Error reading {path}: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() + + + + + + + + + + +#python3 cat.py -b sample-files/* \ No newline at end of file From 07dd81af18256b5233016f20c633871c6653b83f Mon Sep 17 00:00:00 2001 From: mikiyas-stp Date: Tue, 12 Aug 2025 00:06:52 +0100 Subject: [PATCH 4/9] conditional for the options for cat implementation --- implement-shell-tools/cat/cat.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 1c9e32ef..2485cd7e 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -21,6 +21,29 @@ def main(): print(f"Error reading {path}: {e}", file=sys.stderr) sys.exit(1) + # If user used -b + if options.b: + line_num = 1 + numbered_lines = [] + for line in combined_data.split('\n'): + if line.strip() == '': + numbered_lines.append(line) # Keep empty lines unnumbered + else: + numbered_lines.append(f"{line_num:6} {line}") + line_num += 1 + sys.stdout.write('\n'.join(numbered_lines) + '\n') + + # If user used -n + elif options.n: + numbered_lines = [ + f"{i+1:6} {line}" + for i, line in enumerate(combined_data.split('\n')) + ] + sys.stdout.write('\n'.join(numbered_lines) + '\n') + + # If user didn't use -n or -b + else: + sys.stdout.write(combined_data) if __name__ == "__main__": main() From e6867b7b925ac797bef91e82cc614d21c20d4c8e Mon Sep 17 00:00:00 2001 From: mikiyas-stp Date: Tue, 12 Aug 2025 00:08:32 +0100 Subject: [PATCH 5/9] wc implementation --- 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 00000000..74d09fa5 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,15 @@ +import argparse +import sys +import os + + +def main(): + parser = argparse.ArgumentParser(description="Count lines, words, and bytes in files (like wc)") + parser.add_argument("-l", action="store_true", help="count lines") + parser.add_argument("-w", action="store_true", help="count words") + parser.add_argument("-c", action="store_true", help="count bytes") + parser.add_argument("files", nargs="+", help="files to read") + options = parser.parse_args() + +if __name__ == "__main__": + main() From 541562ddb0c82cab794e8f0586116542a5d77449 Mon Sep 17 00:00:00 2001 From: mikiyas-stp Date: Tue, 12 Aug 2025 00:09:23 +0100 Subject: [PATCH 6/9] looping over the files and concatenate it --- implement-shell-tools/wc/wc.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index 74d09fa5..987c47e6 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -11,5 +11,27 @@ def main(): parser.add_argument("files", nargs="+", help="files to read") options = parser.parse_args() + total_lines = total_words = total_bytes = 0 + + for file_path in options.files: + try: + with open(file_path, "r", encoding="utf-8") as f: + data = f.read() + + lines = data.count("\n") + words = len(data.split()) + bytes_ = len(data.encode("utf-8")) + + total_lines += lines + total_words += words + total_bytes += bytes_ + + print(format_output(lines, words, bytes_, os.path.basename(file_path), options)) + except Exception as e: + print(f"Error reading {file_path}: {e}", file=sys.stderr) + + if len(options.files) > 1: + print(format_output(total_lines, total_words, total_bytes, "total", options)) + if __name__ == "__main__": main() From 33d638cf2fc04ba899383440b607b9654c13c675 Mon Sep 17 00:00:00 2001 From: mikiyas-stp Date: Tue, 12 Aug 2025 00:09:59 +0100 Subject: [PATCH 7/9] formatting the output --- implement-shell-tools/wc/wc.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index 987c47e6..26165b0d 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -2,6 +2,18 @@ import sys import os +def format_output(lines, words, bytes_, label, options): + output = "" + if not (options.l or options.w or options.c): + output += f"{lines:8}{words:8}{bytes_:8}" + else: + if options.l: + output += f"{lines:8}" + if options.w: + output += f"{words:8}" + if options.c: + output += f"{bytes_:8}" + return f"{output} {label}" def main(): parser = argparse.ArgumentParser(description="Count lines, words, and bytes in files (like wc)") From 68a9ce0eeb3d4460c1f4dce300f11bbfeae87687 Mon Sep 17 00:00:00 2001 From: mikiyas-stp Date: Thu, 5 Feb 2026 21:17:17 +0000 Subject: [PATCH 8/9] def a new method to read file to make main clean and organised --- implement-shell-tools/cat/cat.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 2485cd7e..ff8cc3c6 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -1,6 +1,17 @@ import argparse import sys +def read_files(paths): + lines = [] + for path in paths: + try: + with open(path, "r", encoding="utf-8") as f: + lines.extend(f.read().splitlines()) + except OSError as e: + print(f"Error reading {path}: {e}", file=sys.stderr) + sys.exit(1) + return lines + def main(): parser = argparse.ArgumentParser( prog="display-content-of-a-file", From 56fa8c18680668682feba3b44e4253fd9ef49979 Mon Sep 17 00:00:00 2001 From: mikiyas-stp Date: Thu, 5 Feb 2026 21:21:20 +0000 Subject: [PATCH 9/9] main simplified using the read_files method: by separating logic and tigher exception handling --- implement-shell-tools/cat/cat.py | 57 +++++++++++--------------------- 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index ff8cc3c6..f6f56f22 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -1,6 +1,7 @@ import argparse import sys + def read_files(paths): lines = [] for path in paths: @@ -11,61 +12,41 @@ def read_files(paths): print(f"Error reading {path}: {e}", file=sys.stderr) sys.exit(1) return lines - + + def main(): parser = argparse.ArgumentParser( prog="display-content-of-a-file", - description="cat is used to display the content of a file or print the content of a file." + description="cat is used to display the content of a file or print the content of a file.", ) - parser.add_argument('-n', action='store_true', help='number output lines') - parser.add_argument('-b', action='store_true', help='number non-empty output lines') - parser.add_argument('paths', nargs='+', help="The file path(s) to process") + + parser.add_argument("-n", action="store_true", help="number output lines") + parser.add_argument("-b", action="store_true", help="number non-empty output lines") + parser.add_argument("paths", nargs="+", help="The file path(s) to process") options = parser.parse_args() - combined_data = "" - for path in options.paths: - try: - with open(path, 'r', encoding='utf-8') as f: - combined_data += f.read() + "\n" - except Exception as e: - print(f"Error reading {path}: {e}", file=sys.stderr) - sys.exit(1) + lines = read_files(options.paths) - # If user used -b if options.b: line_num = 1 - numbered_lines = [] - for line in combined_data.split('\n'): - if line.strip() == '': - numbered_lines.append(line) # Keep empty lines unnumbered - else: - numbered_lines.append(f"{line_num:6} {line}") + for line in lines: + if line.strip(): + print(f"{line_num:6} {line}") line_num += 1 - sys.stdout.write('\n'.join(numbered_lines) + '\n') + else: + print(line) - # If user used -n elif options.n: - numbered_lines = [ - f"{i+1:6} {line}" - for i, line in enumerate(combined_data.split('\n')) - ] - sys.stdout.write('\n'.join(numbered_lines) + '\n') + for i, line in enumerate(lines, start=1): + print(f"{i:6} {line}") - # If user didn't use -n or -b else: - sys.stdout.write(combined_data) + for line in lines: + print(line) + if __name__ == "__main__": main() - - - - - - - - - #python3 cat.py -b sample-files/* \ No newline at end of file