diff --git a/.gitignore b/.gitignore index 3c3629e6..3e5cc695 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +.venv/ \ No newline at end of file diff --git a/implement-shell-tools/cat/my_cat.py b/implement-shell-tools/cat/my_cat.py new file mode 100644 index 00000000..b5227615 --- /dev/null +++ b/implement-shell-tools/cat/my_cat.py @@ -0,0 +1,68 @@ +import argparse +import sys + +parser = argparse.ArgumentParser( + description="Reads and prints one or more files, optionally numbering lines continuously" +) +parser.add_argument("paths", nargs='+', help="One or more file paths") +parser.add_argument("-n", "--number", action="store_true", help="Number all output lines") +parser.add_argument("-b", "--number-nonblank", action="store_true", help="Number non-empty output lines") + +args = parser.parse_args() + +file_paths = args.paths +number_nonblank = args.number_nonblank +number_all = args.number and not number_nonblank + + +total_lines = 0 +total_nonblank_lines = 0 + +for path in file_paths: + try: + with open(path, 'r', encoding='utf-8') as f: + lines = f.readlines() + total_lines += len(lines) + total_nonblank_lines += sum(1 for line in lines if line.strip()) + except Exception as e: + print(f'Error reading file "{path}": {e}', file=sys.stderr) + sys.exit(1) + + +if number_nonblank: + max_digits = len(str(total_nonblank_lines)) +elif number_all: + max_digits = len(str(total_lines)) + +line_number = 1 + + +for path in file_paths: + try: + with open(path, 'r', encoding='utf-8') as f: + lines = f.readlines() + + if number_nonblank: + for line in lines: + if line.strip() == "": + print() + else: + num_str = str(line_number).rjust(max_digits) + print(f"{num_str}\t{line.rstrip()}") + line_number += 1 + + elif number_all: + for line in lines: + num_str = str(line_number).rjust(max_digits) + print(f"{num_str}\t{line.rstrip()}") + line_number += 1 + + else: + for line in lines: + print(line, end='') + if lines and not lines[-1].endswith('\n'): + print() + + except Exception as e: + print(f'Error reading file "{path}": {e}', file=sys.stderr) + sys.exit(1) diff --git a/implement-shell-tools/ls/my_ls.py b/implement-shell-tools/ls/my_ls.py new file mode 100644 index 00000000..341971e7 --- /dev/null +++ b/implement-shell-tools/ls/my_ls.py @@ -0,0 +1,58 @@ +import os +import sys +import stat +import argparse + +parser = argparse.ArgumentParser( + description="List files in a directory (simplified ls implementation)" +) +parser.add_argument("paths", nargs="*", default=["."], help="One or more file or directory paths") +parser.add_argument("-l", "--longList", action="store_true", help="Long listing format") +parser.add_argument("-a", "--all", action="store_true", help="Include hidden files") + +parser.add_argument("-1", "--singleColumn", action="store_true", help="List one file per line") +args = parser.parse_args() + +file_paths = args.paths +show_long = args.longList +show_all = args.all +force_single_column = args.singleColumn + +def format_permissions(mode): + return stat.filemode(mode) + +for input_path in file_paths: + try: + if not os.path.exists(input_path): + raise FileNotFoundError(f'No such file or directory: {input_path}') + + if os.path.isfile(input_path): + if show_long: + file_stat = os.stat(input_path) + perms = format_permissions(file_stat.st_mode) + size = str(file_stat.st_size).rjust(6) + print(f"{perms} {size} {input_path}") + else: + print(input_path) + + elif os.path.isdir(input_path): + entries = os.listdir(input_path) + if not show_all: + entries = [e for e in entries if not e.startswith(".")] + + # Optional: sort entries for consistent output + entries.sort() # (optional for predictable output) + + for entry in entries: + full_path = os.path.join(input_path, entry) + entry_stat = os.stat(full_path) + if show_long: + perms = format_permissions(entry_stat.st_mode) + size = str(entry_stat.st_size).rjust(6) + print(f"{perms} {size} {entry}") + else: + print(entry) + + except Exception as e: + print(f'Error reading "{input_path}": {e}', file=sys.stderr) + sys.exit(1) diff --git a/implement-shell-tools/wc/my_wc.py b/implement-shell-tools/wc/my_wc.py new file mode 100644 index 00000000..d4badc09 --- /dev/null +++ b/implement-shell-tools/wc/my_wc.py @@ -0,0 +1,68 @@ +import os +import sys +import argparse + +# CLI argument parsing +parser = argparse.ArgumentParser(description="Simplified implementation of wc") +parser.add_argument("paths", nargs="*", default=["."], help="One or more file or directory paths") +parser.add_argument("-l", "--line", action="store_true", help="Count lines") +parser.add_argument("-w", "--word", action="store_true", help="Count words") +parser.add_argument("-c", "--character", action="store_true", help="Count characters") + +args = parser.parse_args() + +# If no flags are set, show all +show_all = not (args.line or args.word or args.character) + +# Count content in a string +def count_content(content): + lines = content.splitlines() + words = content.strip().split() + characters = len(content) + return len(lines), len(words), characters + +# Format output line based on flags +def format_output(lines, words, chars, label): + parts = [] + if args.line or show_all: + parts.append(f"{lines:8}") + if args.word or show_all: + parts.append(f"{words:8}") + if args.character or show_all: + parts.append(f"{chars:8}") + parts.append(label) + return " ".join(parts) + +# Totals for multiple files +total = {"lines": 0, "words": 0, "characters": 0} +file_count = 0 + +for input_path in args.paths: + try: + if os.path.isdir(input_path): + print(f"{input_path} is a directory. Skipping.") + continue + + with open(input_path, "r", encoding="utf-8") as f: + content = f.read() + + lines, words, characters = count_content(content) + + total["lines"] += lines + total["words"] += words + total["characters"] += characters + file_count += 1 + + print(format_output(lines, words, characters, input_path)) + + except Exception as e: + print(f'Error reading "{input_path}": {e}', file=sys.stderr) + +# Print totals if more than one file processed +if file_count > 1: + print(format_output( + total["lines"], + total["words"], + total["characters"], + "total" + ))