From 54968b5aa02b423309fe42bdef4f45890869f848 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Sun, 12 Apr 2026 22:10:06 +0100 Subject: [PATCH 1/5] complete wc exercise --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 3c3629e64..531f33b15 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ node_modules + +# Ignore Python virtual environments +implement-cowsay/.venv/ From deb9f0c85a9d159cedc7c47786d69d57d18df749 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Sun, 12 Apr 2026 22:40:05 +0100 Subject: [PATCH 2/5] complete wc exercise2 --- implement-shell-tools/wc/wc.py | 76 ++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 implement-shell-tools/wc/wc.py diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 000000000..ee0423f23 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +import sys +import os + +def count_file(file, options): + try: + with open(file, 'r', encoding='utf-8') as f: + data = f.read() + + lines = data.count('\n') + 1 + words = len(data.split()) + bytes_count = len(data.encode('utf-8')) + + results = [] + if options['lines'] or not any(options.values()): + results.append(lines) + if options['words'] or not any(options.values()): + results.append(words) + if options['bytes'] or not any(options.values()): + results.append(bytes_count) + + print(f"{'\t'.join(map(str, results))}\t{file}") + + return lines, words, bytes_count + except FileNotFoundError: + print(f"wc: {file}: No such file or directory", file=sys.stderr) + sys.exit(1) + +def main(): + args = sys.argv[1:] + options = { + 'lines': False, + 'words': False, + 'bytes': False, + } + + files = [] + + for arg in args: + if arg == '-l': + options['lines'] = True + elif arg == '-w': + options['words'] = True + elif arg == '-c': + options['bytes'] = True + else: + files.append(arg) + + if not files: + print("Usage: wc [-l | -w | -c] ...", file=sys.stderr) + sys.exit(1) + + total_lines = 0 + total_words = 0 + total_bytes = 0 + + for file in files: + lines, words, bytes_count = count_file(file, options) + total_lines += lines + total_words += words + total_bytes += bytes_count + + if len(files) > 1: + total_results = [] + if options['lines'] or not any(options.values()): + total_results.append(total_lines) + if options['words'] or not any(options.values()): + total_results.append(total_words) + if options['bytes'] or not any(options.values()): + total_results.append(total_bytes) + + print(f"{'\t'.join(map(str, total_results))}\ttotal") + +if __name__ == "__main__": + main() \ No newline at end of file From 34e5d9d58be8f93527c30aaabacae6a328b1c5a1 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Sun, 12 Apr 2026 22:43:17 +0100 Subject: [PATCH 3/5] complete cat exercise --- implement-shell-tools/cat/cat.py | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 implement-shell-tools/cat/cat.py diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 000000000..82cfc64f3 --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import sys + +def cat_file(file, options, line_number): + try: + with open(file, 'r', encoding='utf-8') as f: + lines = f.readlines() + + for line in lines: + if options['number_non_blank'] and line.strip(): + print(f"{line_number:6}\t{line}", end='') + line_number += 1 + elif options['number_all']: + print(f"{line_number:6}\t{line}", end='') + line_number += 1 + else: + print(line, end='') + + return line_number + except FileNotFoundError: + print(f"cat: {file}: No such file or directory", file=sys.stderr) + sys.exit(1) + +def main(): + args = sys.argv[1:] + options = { + 'number_all': False, + 'number_non_blank': False, + } + + files = [] + + for arg in args: + if arg == '-n': + options['number_all'] = True + elif arg == '-b': + options['number_non_blank'] = True + else: + files.append(arg) + + if not files: + print("Usage: cat [-n | -b] ...", file=sys.stderr) + sys.exit(1) + + line_number = 1 + for file in files: + line_number = cat_file(file, options, line_number) + +if __name__ == "__main__": + main() \ No newline at end of file From 49c33ed71027b5a41bb3dbb68ba32d12960e8720 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Sun, 12 Apr 2026 22:57:15 +0100 Subject: [PATCH 4/5] Refactor wc.py to streamline file counting and output formatting --- implement-shell-tools/wc/wc.py | 77 ++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index ee0423f23..500ae4657 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -1,32 +1,66 @@ #!/usr/bin/env python3 import sys -import os -def count_file(file, options): + +def count_file(file): try: with open(file, 'r', encoding='utf-8') as f: data = f.read() - lines = data.count('\n') + 1 + lines = data.count('\n') words = len(data.split()) bytes_count = len(data.encode('utf-8')) - results = [] - if options['lines'] or not any(options.values()): - results.append(lines) - if options['words'] or not any(options.values()): - results.append(words) - if options['bytes'] or not any(options.values()): - results.append(bytes_count) - - print(f"{'\t'.join(map(str, results))}\t{file}") - return lines, words, bytes_count except FileNotFoundError: print(f"wc: {file}: No such file or directory", file=sys.stderr) sys.exit(1) + +def selected_keys(options): + if not any(options.values()): + return ['lines', 'words', 'bytes'] + + keys = [] + if options['lines']: + keys.append('lines') + if options['words']: + keys.append('words') + if options['bytes']: + keys.append('bytes') + return keys + + +def values_for_keys(counts, keys): + lines, words, bytes_count = counts + mapping = { + 'lines': lines, + 'words': words, + 'bytes': bytes_count, + } + return [mapping[key] for key in keys] + + +def print_rows(rows, keys): + align_columns = len(keys) > 1 or len(rows) > 1 + + if not align_columns: + values, name = rows[0] + print(f"{values[0]} {name}") + return + + widths = [] + for index in range(len(keys)): + max_len = max(len(str(values[index])) for values, _ in rows) + widths.append(max(3, max_len)) + + for values, name in rows: + formatted_values = " ".join( + f"{value:>{width}}" for value, width in zip(values, widths) + ) + print(f"{formatted_values} {name}") + def main(): args = sys.argv[1:] options = { @@ -54,23 +88,20 @@ def main(): total_lines = 0 total_words = 0 total_bytes = 0 + keys = selected_keys(options) + rows = [] for file in files: - lines, words, bytes_count = count_file(file, options) + lines, words, bytes_count = count_file(file) total_lines += lines total_words += words total_bytes += bytes_count + rows.append((values_for_keys((lines, words, bytes_count), keys), file)) if len(files) > 1: - total_results = [] - if options['lines'] or not any(options.values()): - total_results.append(total_lines) - if options['words'] or not any(options.values()): - total_results.append(total_words) - if options['bytes'] or not any(options.values()): - total_results.append(total_bytes) - - print(f"{'\t'.join(map(str, total_results))}\ttotal") + rows.append((values_for_keys((total_lines, total_words, total_bytes), keys), 'total')) + + print_rows(rows, keys) if __name__ == "__main__": main() \ No newline at end of file From 91a136b6c3cda75fd72b298a2043d09f2a8e4a68 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Wed, 15 Apr 2026 21:53:10 +0100 Subject: [PATCH 5/5] complete ls exercise --- implement-shell-tools/ls/ls.py | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 implement-shell-tools/ls/ls.py diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 000000000..a8e59ef63 --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +import locale +import os +import sys + + +def parse_args(args): + one_per_line = False + show_all = False + paths = [] + + for arg in args: + if arg.startswith("-") and arg != "-": + for flag in arg[1:]: + if flag == "1": + one_per_line = True + elif flag == "a": + show_all = True + else: + print(f"ls: invalid option -- '{flag}'", file=sys.stderr) + sys.exit(1) + else: + paths.append(arg) + + if not one_per_line: + print("Usage: ls.py -1 [-a] [path]", file=sys.stderr) + sys.exit(1) + + if len(paths) > 1: + print("Usage: ls.py -1 [-a] [path]", file=sys.stderr) + sys.exit(1) + + return show_all, (paths[0] if paths else ".") + + +def list_entries(path, show_all): + try: + entries = os.listdir(path) + except FileNotFoundError: + print(f"ls: cannot access '{path}': No such file or directory", file=sys.stderr) + sys.exit(1) + except NotADirectoryError: + print(os.path.basename(path)) + return + + if show_all: + entries = [".", ".."] + entries + else: + entries = [name for name in entries if not name.startswith(".")] + + locale.setlocale(locale.LC_COLLATE, "") + entries = sorted(entries, key=locale.strxfrm) + + for entry in entries: + print(entry) + + +def main(): + show_all, path = parse_args(sys.argv[1:]) + list_entries(path, show_all) + + +if __name__ == "__main__": + main()