diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b242572e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "main" + ] +} \ No newline at end of file diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100755 index 00000000..f32c9c2b --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +import argparse + +# implement-shell-tools/cat/cat.py +def main(): + parser = argparse.ArgumentParser( + prog="cat", + description="Implements a simple version of the 'cat' command to read and display file contents." + ) + + parser.add_argument("-n", help="Number lines", action="store_true") + parser.add_argument("-b", help="Number non-blank lines", action="store_true") + parser.add_argument("path", nargs="+", help="The file to search") + + args = parser.parse_args() + + counter = 1 + + for file_path in args.path: + try: + with open(file_path, 'r') as file: + content = file.read() + lines = content.split("\n") + + for i, line in enumerate(lines): + if i == len(lines) - 1 and line == "": # Skip the last empty line if it exists + break + prefix = "" + if args.b: + if line != "": + prefix = str(counter).rjust(6) + " " + counter += 1 + elif args.n: + prefix = str(counter).rjust(6) + " " + counter += 1 + print(prefix + line) + + except FileNotFoundError: + print(f"cat: {file_path}: No such file or directory") + except Exception as e: + print(f"cat: {file_path}: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 00000000..18e6ed0f --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,66 @@ +# implement-shell-tools/ls/ls.py +import argparse +import os +import sys +import shutil +import math + +def print_columns(items, force_single_column=False): + """Print items in columns unless -1 is passed or output is not a tty.""" + if force_single_column or not sys.stdout.isatty(): + for item in items: + print(item) + return + + # Get terminal width + term_width = shutil.get_terminal_size((80, 20)).columns + + if not items: + return + + # Longest filename length + spacing + max_len = max(len(f) for f in items) + 2 + cols = max(1, term_width // max_len) + rows = math.ceil(len(items) / cols) + + for r in range(rows): + row_items = [] + for c in range(cols): + i = c * rows + r + if i < len(items): + row_items.append(items[i].ljust(max_len)) + print("".join(row_items).rstrip()) + +def main(): + parser = argparse.ArgumentParser( + prog="ls", + description="Implements a simple version of the 'ls' command to list files in a directory." + ) + + parser.add_argument("-1", help="List one file per line", action="store_true") + parser.add_argument("-a", help="Include hidden files", action="store_true") + parser.add_argument("directory", nargs="?", default=".", help="The directory to search") + + args = parser.parse_args() + + try: + if os.path.isdir(args.directory): + files = os.listdir(args.directory) + + if not args.a: + files = [f for f in files if not f.startswith(".")] + + files = sorted(files) + + print_columns(files, force_single_column=args.__dict__["1"]) + else: + # If it's a file, just print the name + print(args.directory) + + except FileNotFoundError: + print(f"ls: {args.directory}: No such file or directory", file=sys.stderr) + except Exception as e: + print(f"ls: {args.directory}: {e}", file=sys.stderr) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 00000000..1eff9fa2 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,65 @@ +import argparse +import sys + +# implement-shell-tools/wc/wc.py +def wc_file(path): + """Return (lines, words, bytes) for a given file.""" + with open(path, "r", encoding="utf-8") as f: + text = f.read() + lines = text.count("\n") + if text and not text.endswith("\n"): + lines += 1 # Count last line if it doesn't end with newline + words = len(text.split()) + chars = len(text.encode("utf-8")) # byte count + return lines, words, chars + +def format_counts(lines, words, chars, args, label): + """Return formatted output string for wc counts.""" + parts = [] + if args.l: + parts.append(str(lines).rjust(8)) + if args.w: + parts.append(str(words).rjust(8)) + if args.c: + parts.append(str(chars).rjust(8)) + if label: + parts.append(label) + return " ".join(parts) + +def main(): + parser = argparse.ArgumentParser( + prog="wc", + description="Implements a simple version of the 'wc' command to count lines, words, and characters in text files." + ) + + 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 characters") + parser.add_argument("path", nargs="+", help="The files to read") + + args = parser.parse_args() + + if not (args.l or args.w or args.c): + args.l = args.w = args.c = True # Default to all counts if none specified + + total_lines = total_words = total_chars = 0 + + for file_path in args.path: + try: + lines, words, chars = wc_file(file_path) + total_lines += lines + total_words += words + total_chars += chars + + print(format_counts(lines, words, chars, args, file_path)) + + except FileNotFoundError: + print(f"wc: {file_path}: No such file or directory", file=sys.stderr) + except Exception as e: + print(f"wc: {file_path}: {e}", file=sys.stderr) + + if len(args.path) > 1: + print(format_counts(total_lines, total_words, total_chars, args, "total")) + +if __name__ == "__main__": + main() \ No newline at end of file