diff --git a/implement-shell-tools/cat/script-02.py b/implement-shell-tools/cat/script-02.py new file mode 100644 index 00000000..4788ec24 --- /dev/null +++ b/implement-shell-tools/cat/script-02.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +import sys +import argparse + +def read_file(path, number_all, number_nonblank, line_counter): + """Reads a file and prints its contents with the requested numbering.""" + try: + with open(path, "r") as f: + for line in f: + line_no_newline = line.rstrip("\n") + + if number_all: + print(f"{line_counter:6}\t{line_no_newline}") + line_counter += 1 + + elif number_nonblank: + if line_no_newline.strip(): + print(f"{line_counter:6}\t{line_no_newline}") + line_counter += 1 + else: + print("") # blank line stays blank + + else: + print(line_no_newline) + + return line_counter + + except FileNotFoundError: + print(f"cat: {path}: No such file or directory", file=sys.stderr) + return line_counter + + +def main(): + parser = argparse.ArgumentParser(description="Simple cat implementation") + parser.add_argument("-n", action="store_true", help="number all lines") + parser.add_argument("-b", action="store_true", help="number non-blank lines") + parser.add_argument("files", nargs="+", help="files to read") + + args = parser.parse_args() + + # cat rule: if -b is used, ignore -n + number_all = args.n + number_nonblank = args.b + if number_nonblank: + number_all = False + + line_counter = 1 + + for path in args.files: + line_counter = read_file(path, number_all, number_nonblank, line_counter) + + +if __name__ == "__main__": + main() diff --git a/implement-shell-tools/cat/script-cat.py b/implement-shell-tools/cat/script-cat.py new file mode 100644 index 00000000..4788ec24 --- /dev/null +++ b/implement-shell-tools/cat/script-cat.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +import sys +import argparse + +def read_file(path, number_all, number_nonblank, line_counter): + """Reads a file and prints its contents with the requested numbering.""" + try: + with open(path, "r") as f: + for line in f: + line_no_newline = line.rstrip("\n") + + if number_all: + print(f"{line_counter:6}\t{line_no_newline}") + line_counter += 1 + + elif number_nonblank: + if line_no_newline.strip(): + print(f"{line_counter:6}\t{line_no_newline}") + line_counter += 1 + else: + print("") # blank line stays blank + + else: + print(line_no_newline) + + return line_counter + + except FileNotFoundError: + print(f"cat: {path}: No such file or directory", file=sys.stderr) + return line_counter + + +def main(): + parser = argparse.ArgumentParser(description="Simple cat implementation") + parser.add_argument("-n", action="store_true", help="number all lines") + parser.add_argument("-b", action="store_true", help="number non-blank lines") + parser.add_argument("files", nargs="+", help="files to read") + + args = parser.parse_args() + + # cat rule: if -b is used, ignore -n + number_all = args.n + number_nonblank = args.b + if number_nonblank: + number_all = False + + line_counter = 1 + + for path in args.files: + line_counter = read_file(path, number_all, number_nonblank, line_counter) + + +if __name__ == "__main__": + main() diff --git a/implement-shell-tools/ls/script-02.py b/implement-shell-tools/ls/script-02.py new file mode 100644 index 00000000..79afe58b --- /dev/null +++ b/implement-shell-tools/ls/script-02.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse + + +def list_directory(path, show_all): + """List contents of a directory, respecting the -a and -1 options.""" + + try: + entries = os.listdir(path) + except FileNotFoundError: + print(f"ls: cannot access '{path}': No such file or directory", file=sys.stderr) + return + + # If -a is not provided, hide dotfiles + if not show_all: + entries = [e for e in entries if not e.startswith(".")] + + # Sort alphabetically like ls normally does + entries.sort() + + # Print one entry per line (-1 behaviour) + for entry in entries: + print(entry) + + +def main(): + parser = argparse.ArgumentParser(description="Simple ls implementation") + parser.add_argument( + "-a", + action="store_true", + help="include directory entries whose names begin with a dot", + ) + parser.add_argument( + "-1", + dest="one_per_line", + action="store_true", + help="list one file per line", + ) + parser.add_argument("path", nargs="?", default=".", help="directory to list") + + args = parser.parse_args() + + # Only -1 is supported, but it's always required in this assignment + if not args.one_per_line: + print("This program only supports the -1 option.", file=sys.stderr) + sys.exit(1) + + list_directory(args.path, show_all=args.a) + + +if __name__ == "__main__": + main() diff --git a/implement-shell-tools/ls/script-ls.py b/implement-shell-tools/ls/script-ls.py new file mode 100644 index 00000000..9166e5f2 --- /dev/null +++ b/implement-shell-tools/ls/script-ls.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse + + +def list_directory(path, show_all, one_per_line): + try: + # If path is a file, just print it + if os.path.isfile(path): + print(path) + return + + # If path is a directory, list its contents + if os.path.isdir(path): + entries = os.listdir(path) + else: + print( + f"ls: cannot access '{path}': No such file or directory", + file=sys.stderr, + ) + return + + except PermissionError: + print( + f"ls: cannot open directory '{path}': Permission denied", + file=sys.stderr, + ) + return + + # Hide dotfiles unless -a is used + if not show_all: + entries = [e for e in entries if not e.startswith(".")] + + entries.sort() + + # Output formatting + if one_per_line: + for entry in entries: + print(entry) + else: + print(" ".join(entries)) + + +def main(): + parser = argparse.ArgumentParser(description="Simple ls implementation") + + parser.add_argument( + "-a", + action="store_true", + help="include directory entries whose names begin with a dot", + ) + + parser.add_argument( + "-1", + dest="one_per_line", + action="store_true", + help="list one file per line", + ) + + parser.add_argument( + "path", + nargs="?", + default=".", + help="directory or file to list", + ) + + args = parser.parse_args() + + list_directory( + args.path, + show_all=args.a, + one_per_line=args.one_per_line, + ) + + +if __name__ == "__main__": + main() diff --git a/implement-shell-tools/wc/script-02.py b/implement-shell-tools/wc/script-02.py new file mode 100644 index 00000000..e7d9490b --- /dev/null +++ b/implement-shell-tools/wc/script-02.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +import argparse +import sys + + +def count_file(path): + """Return (lines, words, bytes) for the given file.""" + try: + with open(path, "rb") as f: + content = f.read() + except FileNotFoundError: + print(f"wc: {path}: No such file or directory", file=sys.stderr) + return None + + text = content.decode("utf-8", errors="replace") + lines = text.count("\n") + words = len(text.split()) + bytes_ = len(content) + + return lines, words, bytes_ + + +def print_result(counts, path, show_l, show_w, show_c): + """Format and print output for a file.""" + lines, words, bytes_ = counts + + # If no specific flag is given → print all three (like wc) + if not (show_l or show_w or show_c): + print(f"{lines:>7} {words:>7} {bytes_:>7} {path}") + return + + out = [] + if show_l: + out.append(f"{lines:>7}") + if show_w: + out.append(f"{words:>7}") + if show_c: + out.append(f"{bytes_:>7}") + + print(" ".join(out), path) + + +def main(): + parser = argparse.ArgumentParser(description="Simple wc implementation") + 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("paths", nargs="+", help="file(s) to process") + + args = parser.parse_args() + + total_lines = total_words = total_bytes = 0 + multiple_files = len(args.paths) > 1 + + for path in args.paths: + result = count_file(path) + if result is None: + continue + + lines, words, bytes_ = result + print_result(result, path, args.l, args.w, args.c) + + total_lines += lines + total_words += words + total_bytes += bytes_ + + # Print totals if more than one file + if multiple_files: + if not (args.l or args.w or args.c): + print(f"{total_lines:>7} {total_words:>7} {total_bytes:>7} total") + else: + out = [] + if args.l: + out.append(f"{total_lines:>7}") + if args.w: + out.append(f"{total_words:>7}") + if args.c: + out.append(f"{total_bytes:>7}") + print(" ".join(out), "total") + + +if __name__ == "__main__": + main() diff --git a/implement-shell-tools/wc/script-wc.py b/implement-shell-tools/wc/script-wc.py new file mode 100644 index 00000000..e7d9490b --- /dev/null +++ b/implement-shell-tools/wc/script-wc.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +import argparse +import sys + + +def count_file(path): + """Return (lines, words, bytes) for the given file.""" + try: + with open(path, "rb") as f: + content = f.read() + except FileNotFoundError: + print(f"wc: {path}: No such file or directory", file=sys.stderr) + return None + + text = content.decode("utf-8", errors="replace") + lines = text.count("\n") + words = len(text.split()) + bytes_ = len(content) + + return lines, words, bytes_ + + +def print_result(counts, path, show_l, show_w, show_c): + """Format and print output for a file.""" + lines, words, bytes_ = counts + + # If no specific flag is given → print all three (like wc) + if not (show_l or show_w or show_c): + print(f"{lines:>7} {words:>7} {bytes_:>7} {path}") + return + + out = [] + if show_l: + out.append(f"{lines:>7}") + if show_w: + out.append(f"{words:>7}") + if show_c: + out.append(f"{bytes_:>7}") + + print(" ".join(out), path) + + +def main(): + parser = argparse.ArgumentParser(description="Simple wc implementation") + 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("paths", nargs="+", help="file(s) to process") + + args = parser.parse_args() + + total_lines = total_words = total_bytes = 0 + multiple_files = len(args.paths) > 1 + + for path in args.paths: + result = count_file(path) + if result is None: + continue + + lines, words, bytes_ = result + print_result(result, path, args.l, args.w, args.c) + + total_lines += lines + total_words += words + total_bytes += bytes_ + + # Print totals if more than one file + if multiple_files: + if not (args.l or args.w or args.c): + print(f"{total_lines:>7} {total_words:>7} {total_bytes:>7} total") + else: + out = [] + if args.l: + out.append(f"{total_lines:>7}") + if args.w: + out.append(f"{total_words:>7}") + if args.c: + out.append(f"{total_bytes:>7}") + print(" ".join(out), "total") + + +if __name__ == "__main__": + main()