From e20c15675e2a244ea91919254653c32dec9edbc3 Mon Sep 17 00:00:00 2001 From: Matthew Justice Date: Mon, 26 Oct 2026 12:34:55 -0400 Subject: [PATCH] Implement `ls` and `wc` command line tools in Python --- implement-shell-tools/ls/README.md | 3 ++ implement-shell-tools/ls/ls.py | 27 +++++++++++++++++ implement-shell-tools/wc/README.md | 5 ++++ implement-shell-tools/wc/wc.py | 47 ++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 implement-shell-tools/ls/ls.py create mode 100644 implement-shell-tools/wc/wc.py diff --git a/implement-shell-tools/ls/README.md b/implement-shell-tools/ls/README.md index edbfb81..8b41add 100644 --- a/implement-shell-tools/ls/README.md +++ b/implement-shell-tools/ls/README.md @@ -13,3 +13,6 @@ It must act the same as `ls` would, if run from the directory containing this RE Matching any additional behaviours or flags are optional stretch goals. We recommend you start off supporting just `-1`, then adding support for `-a`. + +Run the Python implementation from this directory with `python ls.py -1 sample-files` +or `python ls.py -1 -a sample-files`. Python 3 is required. diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 0000000..4ef928c --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,27 @@ +"""List directory entries, one per line.""" + +import argparse +from pathlib import Path +import sys + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("-1", action="store_true", help="print one entry per line") + parser.add_argument("-a", "--all", action="store_true", help="include hidden entries") + parser.add_argument("directory", nargs="?", default=".") + args = parser.parse_args() + + try: + entries = sorted(Path(args.directory).iterdir(), key=lambda entry: entry.name) + for entry in entries: + if args.all or not entry.name.startswith("."): + print(entry.name) + except OSError as error: + print(f"ls: {error}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/implement-shell-tools/wc/README.md b/implement-shell-tools/wc/README.md index bd76b65..44eba5a 100644 --- a/implement-shell-tools/wc/README.md +++ b/implement-shell-tools/wc/README.md @@ -15,3 +15,8 @@ It must act the same as `wc` would, if run from the directory containing this RE Matching any additional behaviours or flags are optional stretch goals. We recommend you start off supporting no flags for one file, then add support for multiple files, then add support for the flags. + +Run the Python implementation from this directory with `python wc.py sample-files/1.txt` +or `python wc.py -l sample-files/3.txt`. Multiple filenames and combined count +flags are supported. Expand wildcards in your shell, or pass filenames explicitly +if your shell does not expand them. Python 3 is required. diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 0000000..3153db8 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,47 @@ +"""Count lines, words, and bytes in files.""" + +import argparse +from pathlib import Path +import sys + + +def count_file(filename): + text = Path(filename).read_bytes().decode("utf-8") + return len(text.splitlines()), len(text.split()), len(text) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("-l", action="store_true", help="print line counts") + parser.add_argument("-w", action="store_true", help="print word counts") + parser.add_argument("-c", action="store_true", help="print byte counts") + parser.add_argument("files", nargs="+") + args = parser.parse_args() + selected = [args.l, args.w, args.c] + if not any(selected): + selected = [True, True, True] + + def print_counts(counts, label): + values = [str(value) for value, enabled in zip(counts, selected) if enabled] + print(" ".join([*values, label])) + + totals = [0, 0, 0] + successful = 0 + status = 0 + for filename in args.files: + try: + counts = count_file(filename) + except (OSError, UnicodeError) as error: + print(f"wc: {filename}: {error}", file=sys.stderr) + status = 1 + continue + print_counts(counts, filename) + totals = [total + value for total, value in zip(totals, counts)] + successful += 1 + if successful > 1: + print_counts(totals, "total") + return status + + +if __name__ == "__main__": + sys.exit(main())