diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 00000000..c1078ed9 --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +import sys +import argparse + +# Create the argument parser – similar style to `count-containing-words` +parser = argparse.ArgumentParser( + prog="cat", + description="Prints the contents of one or more files", +) + +# positional argument: one or more paths +parser.add_argument( + "paths", + nargs="+", + help="The file(s) to print", +) + +args = parser.parse_args() + +# Loop over each path and print its content +for path in args.paths: + try: + with open(path, "r", encoding="utf-8") as f: + for line in f: + # print line exactly as it appears in the file + sys.stdout.write(line) + except FileNotFoundError: + # match shell cat-style error message + sys.stderr.write(f"cat: {path}: No such file or directory\n") diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 00000000..af73ad69 --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse + +parser = argparse.ArgumentParser( + prog="ls", + description="Simple reimplementation of ls", +) + +parser.add_argument( + "-1", + dest="one_per_line", + action="store_true", + help="List one entry per line", +) + +parser.add_argument( + "-a", + dest="all_files", + action="store_true", + help="Do not ignore entries starting with .", +) + +# Optional path (default = current directory) +parser.add_argument( + "path", + nargs="?", + default=".", + help="Directory to list (default: current directory)", +) + +args = parser.parse_args() + +# Get all items in the directory +try: + entries = os.listdir(args.path) +except FileNotFoundError: + sys.stderr.write(f"ls: cannot access '{args.path}': No such file or directory\n") + sys.exit(1) + +# Handle -a (hidden files) +if not args.all_files: + entries = [e for e in entries if not e.startswith(".")] + +# Sort for stable behaviour +entries.sort() + +# Output: -1 = one per line +if args.one_per_line: + for entry in entries: + print(entry) +else: + # default: space-separated + print(" ".join(entries)) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 00000000..fd552a99 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 + +import argparse +import sys +import os + +parser = argparse.ArgumentParser( + prog="wc", + description="Simple reimplementation of wc" +) + +parser.add_argument( + "-l", + dest="count_lines", + action="store_true", + help="Print the line counts" +) + +parser.add_argument( + "-w", + dest="count_words", + action="store_true", + help="Print the word counts" +) + +parser.add_argument( + "-c", + dest="count_bytes", + action="store_true", + help="Print the byte counts" +) + +parser.add_argument( + "paths", + nargs="+", + help="File(s) to process" +) + +args = parser.parse_args() + + +def wc_file(path): + """Return (lines, words, bytes) for a file.""" + try: + with open(path, "r", encoding="utf-8") as f: + content = f.read() + except FileNotFoundError: + sys.stderr.write(f"wc: {path}: No such file or directory\n") + return None + + lines = content.count("\n") + (1 if content and not content.endswith("\n") else 0) + words = len(content.split()) + bytes_ = len(content.encode("utf-8")) + + return lines, words, bytes_ + + +# If no flags are supplied, show all three +show_all = not (args.count_lines or args.count_words or args.count_bytes) + +totals = [0, 0, 0] # lines, words, bytes +multiple_files = len(args.paths) > 1 + +for path in args.paths: + result = wc_file(path) + if result is None: + continue + + lines, words, bytes_ = result + + totals[0] += lines + totals[1] += words + totals[2] += bytes_ + + output_parts = [] + + if show_all: + output_parts.extend([str(lines), str(words), str(bytes_)]) + else: + if args.count_lines: + output_parts.append(str(lines)) + if args.count_words: + output_parts.append(str(words)) + if args.count_bytes: + output_parts.append(str(bytes_)) + + output_parts.append(path) + print(" ".join(output_parts)) + + +# If multiple files → print total line +if multiple_files: + totals_output = [] + + if show_all: + totals_output.extend([str(totals[0]), str(totals[1]), str(totals[2])]) + else: + if args.count_lines: + totals_output.append(str(totals[0])) + if args.count_words: + totals_output.append(str(totals[1])) + if args.count_bytes: + totals_output.append(str(totals[2])) + + totals_output.append("total") + print(" ".join(totals_output))