diff --git a/.gitignore b/.gitignore index c4234ab74..ca06beb44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules +venv testoutput.txt diff --git a/implement-shell-tools/.gitignore b/implement-shell-tools/.gitignore new file mode 100644 index 000000000..0cafc1cde --- /dev/null +++ b/implement-shell-tools/.gitignore @@ -0,0 +1 @@ +.venv/ \ No newline at end of file diff --git a/implement-shell-tools/cat/mycat.py b/implement-shell-tools/cat/mycat.py new file mode 100644 index 000000000..d3546075f --- /dev/null +++ b/implement-shell-tools/cat/mycat.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 + +import sys +import glob + +def main(): + args = sys.argv[1:] + + if not args: + print("Usage: cat [-n|-b] file...", file=sys.stderr) + sys.exit(1) + + number_all = False + number_nonempty = False + paths = [] + + for a in args: + if a == "-n": + number_all = True + elif a == "-b": + number_nonempty = True + else: + paths.append(a) + + if number_nonempty: + number_all = False + + files = expand_paths(paths) + + had_error = print_lines( + files, + number_all=number_all, + number_nonempty=number_nonempty, + ) + + if had_error: + sys.exit(1) + + +def expand_paths(paths): + """Expand glob patterns and return sorted unique file list.""" + files = [] + for p in paths: + matches = glob.glob(p) + if matches: + files.extend(matches) + else: + files.append(p) # keep as-is (will error later if missing) + return sorted(files) + +def read_lines(file): + with open(file, "r", encoding="utf-8") as f: + return f.readlines() + +def print_lines(files, number_all=False, number_nonempty=False): + had_error = False + line_no = 1 + for file in files: + try: + lines = read_lines(file) + except FileNotFoundError: + print(f"cat: {file}: No such file or directory", file=sys.stderr) + had_error = True + continue + + for line in lines: + is_empty = (line.strip() == "") + + if number_nonempty: + if not is_empty: + prefix = f"{line_no:6}\t" + line_no += 1 + else: + prefix = "" + elif number_all: + prefix = f"{line_no:6}\t" + line_no += 1 + else: + prefix = "" + + # avoid double newlines: line already includes '\n' + sys.stdout.write(prefix + line) + + return had_error + +if __name__ == "__main__": + main() + + \ No newline at end of file diff --git a/implement-shell-tools/ls/my-ls.py b/implement-shell-tools/ls/my-ls.py new file mode 100644 index 000000000..748a6d7b6 --- /dev/null +++ b/implement-shell-tools/ls/my-ls.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +import sys +import os + + + +def main(): + args = sys.argv[1:] + + show_all = False + one_per_line = False + paths = [] + + for a in args: + if a == "-a": + show_all = True + elif a == "-1": + one_per_line = True + else: + paths.append(a) + + if not paths: + paths = ["."] + + had_error = False + + for path in paths: + if list_dir( + path, + show_all=show_all, + one_per_line=one_per_line, + ): + had_error = True + + if had_error: + sys.exit(1) + + +def list_dir(path, show_all=False, one_per_line=False): + try: + entries = os.listdir(path) + except FileNotFoundError: + print(f"ls: cannot access '{path}': No such file or directory", + file=sys.stderr, + ) + return True + + entries = sorted(entries) + + if show_all: + normal = sorted([e for e in entries if not e.startswith('.')]) + hidden = sorted([e for e in entries if e.startswith('.')]) + + entries = [".", ".."] + normal + hidden + else: + entries = sorted([e for e in entries if not e.startswith('.')]) + + for entry in entries: + print(entry) + + return False + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/implement-shell-tools/wc/my-wc.py b/implement-shell-tools/wc/my-wc.py new file mode 100644 index 000000000..1a43a36c8 --- /dev/null +++ b/implement-shell-tools/wc/my-wc.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +import sys +import glob + +def count_file(path): + with open(path, "rb") as f: + content = f.read() + + byte_count = len(content) + text = content.decode("utf-8", errors="ignore") + + line_count = text.count("\n") + word_count = len(text.split()) + + return line_count, word_count, byte_count + +def expand(paths): + files = [] + for p in paths: + matches = glob.glob(p) + if matches: + files.extend(matches) + else: + files.append(p) + return files + +def main(): + args = sys.argv[1:] + + show_l = False + show_w = False + show_c = False + + paths = [] + + + # parse args + for a in args: + if a == "-l": + show_l = True + elif a == "-w": + show_w = True + elif a == "-c": + show_c = True + else: + paths.append(a) + + # default: show all + if not (show_l or show_w or show_c): + show_l = show_w = show_c = True + + files = expand(paths) + + total_l = 0 + total_w = 0 + total_c = 0 + + results = [] + + for file in files: + try: + l, w, c = count_file(file) + except FileNotFoundError: + print(f"wc: {file}: No such file or directory", file=sys.stderr) + continue + + total_l += l + total_w += w + total_c += c + + results.append((l,w,c, file)) + + # print per-file results (GNU-aligned formatting) + for l, w, c, file in results: + + parts = [] + if show_l: + parts.append(f"{l:3}") + if show_w: + parts.append(f"{w:4}") + if show_c: + parts.append(f"{c:4}") + + + print("".join(parts) + " " + file) + + # print total if multiple files + if len(results) > 1: + parts = [] + + if show_l: + parts.append(f"{total_l:3}") + if show_w: + parts.append(f"{total_w:4}") + if show_c: + parts.append(f"{total_c:4}") + + + print("".join(parts) + " total") + + +if __name__ == "__main__": + main() \ No newline at end of file