diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 00000000..acbd3a7a --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,44 @@ +import argparse + + +parser = argparse.ArgumentParser( + prog="cat", + description="An alternative to the 'cat' command", +) + +parser.add_argument("files", nargs="+", help="The file(s) to process") +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-blank output lines") + +args = parser.parse_args() + + + +def print_line(line, padding_width, line_number=None): + """Helper function to print a line with optional line numbering.""" + if line_number is not None: + print(f"{str(line_number).rjust(padding_width)} {line}") + else: + print(line) + +line_number = 1 +padding_width = 6 + +for file in args.files: + with open(file, "r") as f: + # read the content and split the lines ready to process as needed + content = f.read().splitlines() + + for line in content: + # use .strip() to remove leading and trailing whitespace or /n + if args.number_nonblank and line.strip(): + # number non-blank lines only + print_line(line, padding_width, line_number) + line_number += 1 + elif args.number: + # number all lines + print_line(line, padding_width, line_number) + line_number += 1 + else: + # no flags + print_line(line, padding_width) \ 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..3bebdd59 --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,47 @@ +import os +import argparse +import locale + + +#set locale to the machine default setting +locale.setlocale(locale.LC_ALL, '') + + +parser = argparse.ArgumentParser( + prog="ls", + description="An alternative to the 'ls' command", +) + +parser.add_argument("directory", nargs="?", default=".", help="The directory to list (defaults to the current directory)") +parser.add_argument("-1", "--single-column", action="store_true", help="List all files, one per line") +parser.add_argument("-a", "--all", action="store_true", help="Include hidden files (those starting with .) in the listing") + +args = parser.parse_args() + +# check if path exists +if not os.path.exists(args.directory): + print(f"ls: cannot access '${args.directory}': No such file or directory") + exit(1) + +# check if path is a directory +if os.path.isdir(args.directory): + entries = os.listdir(args.directory) + + # if -a flag set + if args.all: + entries.extend(['.', '..']) + + # filter hidden files if -a (all) flag not set + if not args.all: + entries = [entry for entry in entries if not entry.startswith(".")] + + # sort the entries using locale-aware comparison + entries.sort(key =locale.strxfrm) + + # print entries + if args.single_column: + for entry in entries: + print(entry) + else: + print(" ".join(entries)) + \ 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..d7246ec2 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,68 @@ +import argparse +import locale + + +#set locale to the machine default setting +locale.setlocale(locale.LC_ALL, '') + +parser = argparse.ArgumentParser( + prog="wc", + description="An alternative to the 'wc' command", +) +parser.add_argument("files", nargs="+", help="The file(s) to process") +parser.add_argument("-l", "--lines", action="store_true", help="Print the newline counts") +parser.add_argument("-w", "--words", action="store_true", help="Print the word counts") +parser.add_argument("-c", "--bytes", action="store_true", help="Print the byte counts") + +args = parser.parse_args() + +# when no specific flags are set +no_flags = not (args.lines or args.words or args.bytes) + +totals = {"lines": 0, "words": 0, "bytes": 0} + +def calculate_counts(content): + # returns a count object + return { + "lines": content.count('\n') + (1 if content and not content.endswith('\n') else 0), # count lines by '\n'; add 1 if last line has no newline to match real wc command behavior" + "words": len(content.split()), + "bytes": len(content.encode('utf-8')), + } + + +def print_counts(counts, file): + parts = [] + # map flag args to the keys in the count object + flag_arg_to_key = { + "lines": args.lines, + "words": args.words, + "bytes": args.bytes + } + + PADDING = 3 + + for key, flag in flag_arg_to_key.items(): + if no_flags or flag: + # apply padding only when no_flags is True + padded_output = f"{counts[key]:>{PADDING}}" if no_flags else str(counts[key]) + parts.append(padded_output) + parts.append(file) + print(" ".join(parts)) + + +for file in args.files: + try: + with open(file, "r") as f: + # read the content and split the lines ready to process as needed + content = f.read() + counts = calculate_counts(content) + # update totals object with values from count object + for key in totals: + totals[key] += counts[key] + print_counts(counts, file) + except FileNotFoundError: + print(f"wc: {file}: No such file or directory found") + +# Print totals if more than one file +if len(args.files) > 1: + print_counts(totals, "total")