From 3a31fbfcb8358f0e84af85cb2c25cc3b92a4d281 Mon Sep 17 00:00:00 2001 From: Luke-Manyamazi Date: Fri, 1 Aug 2025 05:15:58 +0200 Subject: [PATCH 1/6] added python the code for the cat command --- implement-shell-tools/cat/cat.py | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 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 00000000..94b56ccb --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,38 @@ +import glob +import argparse + +def cat(filepath, n=False, b=False, line_counter=None): + try: + with open(filepath) as f: + for line in f: + if b: + if line.strip(): + print(f"{line_counter[0]:6}\t{line}", end='') + line_counter[0] += 1 + else: + print(line, end='') + elif n: + print(f"{line_counter[0]:6}\t{line}", end='') + line_counter[0] += 1 + else: + print(line, end='') + except FileNotFoundError: + print(f"cat: {filepath}: No such file or directory") + +def main(): + parser = argparse.ArgumentParser(description = "Concatenate files and print on the standard output.") + parser.add_argument('-n', action='store_true', help='number all output lines') + parser.add_argument('-b', action='store_true', help='number non-empty output lines') + parser.add_argument('files', nargs='+', help='files to concatenate') + args = parser.parse_args() + + files = [] + for pattern in args.files: + files.extend(glob.glob(pattern) or [pattern]) + + line_counter = [1] + for file in sorted(files): + cat(file, args.n, args.b, line_counter) + +if __name__ == "__main__": + main() \ No newline at end of file From d44366990a40765336038e8cbe6e10cdb3da109e Mon Sep 17 00:00:00 2001 From: Luke-Manyamazi Date: Fri, 1 Aug 2025 05:36:29 +0200 Subject: [PATCH 2/6] added python code for the ls command --- implement-shell-tools/ls/ls.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 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 00000000..2f4204bb --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,28 @@ +import os +import argparse + +def ls(path='.', one_column=False, show_hidden=False): + try: + files = os.listdir(path) + if not show_hidden: + files = [f for f in files if not f.startswith('.')] + files.sort() + + if one_column: + print(*files, sep='\n') + else: + print(*files) + except FileNotFoundError: + print(f"ls: cannot access '{path}': No such file or directory") + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-1', action='store_true', help='list one file per line') + parser.add_argument('-a', action='store_true', help='show hidden files') + parser.add_argument('path', nargs='?', default='.', help='directory to list') + args = parser.parse_args() + + ls(args.path, args.__dict__['1'], args.a) + +if __name__ == "__main__": + main() \ No newline at end of file From 1900a289319bc3d463c83f006576a2cedcf72956 Mon Sep 17 00:00:00 2001 From: Luke-Manyamazi Date: Sat, 2 Aug 2025 15:33:45 +0200 Subject: [PATCH 3/6] added a destination to handel the -1 as a string --- implement-shell-tools/ls/ls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index 2f4204bb..d7499b3a 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -17,12 +17,12 @@ def ls(path='.', one_column=False, show_hidden=False): def main(): parser = argparse.ArgumentParser() - parser.add_argument('-1', action='store_true', help='list one file per line') + parser.add_argument('-1', dest='one_column', action='store_true', help='list one file per line') parser.add_argument('-a', action='store_true', help='show hidden files') parser.add_argument('path', nargs='?', default='.', help='directory to list') args = parser.parse_args() - ls(args.path, args.__dict__['1'], args.a) + ls(args.path, args.one_column, args.a) if __name__ == "__main__": main() \ No newline at end of file From ff86f10f0265a92313d00b3f1f0e012ebb3e86b5 Mon Sep 17 00:00:00 2001 From: Luke-Manyamazi Date: Tue, 5 Aug 2025 15:58:11 +0200 Subject: [PATCH 4/6] added python code for the wc exercise --- implement-shell-tools/wc/wc.py | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 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 00000000..a658c87f --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,37 @@ +import argparse + +def wc(path, count_lines, count_words, count_bytes): + try: + with open(path, 'r') as f: + content = f.read() + lines = content.splitlines() + words = content.split() + bytes_ = len(content.encode('utf-8')) + + parts = [] + if count_lines: parts.append(str(len(lines))) + if count_words: parts.append(str(len(words))) + if count_bytes: parts.append(str(bytes_)) + + if not parts: + parts = [str(len(lines)), str(len(words)), str(bytes_)] + print(' '.join(parts), path) + + except FileNotFoundError: + print(f"wc: {path}: No such file or directory") + except IsADirectoryError: + print(f"wc: {path}: Is a directory") + +def main(): + parser = argparse.ArgumentParser() + 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 bytes') + parser.add_argument('paths', nargs='+', help='Files to count') + args = parser.parse_args() + + for path in args.paths: + wc(path, args.l, args.w, args.c) + +if __name__ == "__main__": + main() \ No newline at end of file From fcac82c47e34fefba885d896876aeab526ea5af2 Mon Sep 17 00:00:00 2001 From: Luke Manyamazi Date: Mon, 13 Oct 2025 20:35:11 +0200 Subject: [PATCH 5/6] replace -n/-b flags with Numbering Enum --- implement-shell-tools/cat/cat.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 94b56ccb..4eb2eab0 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -1,17 +1,23 @@ import glob import argparse +from enum import Enum -def cat(filepath, n=False, b=False, line_counter=None): +class Numbering(Enum): + NONE = 0 + ALL = 1 + NONEMPTY = 2 + +def cat(filepath, numbering=Numbering.NONE, line_counter=None): try: with open(filepath) as f: for line in f: - if b: + if numbering == Numbering.NONEMPTY: if line.strip(): print(f"{line_counter[0]:6}\t{line}", end='') line_counter[0] += 1 else: print(line, end='') - elif n: + elif numbering == Numbering.ALL: print(f"{line_counter[0]:6}\t{line}", end='') line_counter[0] += 1 else: @@ -20,7 +26,7 @@ def cat(filepath, n=False, b=False, line_counter=None): print(f"cat: {filepath}: No such file or directory") def main(): - parser = argparse.ArgumentParser(description = "Concatenate files and print on the standard output.") + parser = argparse.ArgumentParser(description="Concatenate files and print on the standard output.") parser.add_argument('-n', action='store_true', help='number all output lines') parser.add_argument('-b', action='store_true', help='number non-empty output lines') parser.add_argument('files', nargs='+', help='files to concatenate') @@ -31,8 +37,19 @@ def main(): files.extend(glob.glob(pattern) or [pattern]) line_counter = [1] + + # Determine numbering mode (mutually exclusive) + if args.n and args.b: + parser.error("options -n and -b are mutually exclusive") + elif args.n: + numbering = Numbering.ALL + elif args.b: + numbering = Numbering.NONEMPTY + else: + numbering = Numbering.NONE + for file in sorted(files): - cat(file, args.n, args.b, line_counter) + cat(file, numbering=numbering, line_counter=line_counter) if __name__ == "__main__": - main() \ No newline at end of file + main() From 5476f45a6737a8ec24be31d617bc7ef5ef810f26 Mon Sep 17 00:00:00 2001 From: Luke Manyamazi Date: Mon, 13 Oct 2025 21:16:03 +0200 Subject: [PATCH 6/6] refactored and corrected code to meet and answer the asked questions for all the files --- implement-shell-tools/cat/cat.py | 27 ++++++++++++--------------- implement-shell-tools/ls/ls.py | 7 +++++-- implement-shell-tools/wc/wc.py | 29 +++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 4eb2eab0..58dccaee 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -1,4 +1,3 @@ -import glob import argparse from enum import Enum @@ -7,23 +6,27 @@ class Numbering(Enum): ALL = 1 NONEMPTY = 2 -def cat(filepath, numbering=Numbering.NONE, line_counter=None): +def print_numbered_line(line, line_number, pad=6): + print(f"{line_number:{pad}}\t{line}", end='') + return line_number + 1 + +def cat(filepath, numbering, start_line): + line_number = start_line try: with open(filepath) as f: for line in f: if numbering == Numbering.NONEMPTY: if line.strip(): - print(f"{line_counter[0]:6}\t{line}", end='') - line_counter[0] += 1 + line_number = print_numbered_line(line, line_number) else: print(line, end='') elif numbering == Numbering.ALL: - print(f"{line_counter[0]:6}\t{line}", end='') - line_counter[0] += 1 + line_number = print_numbered_line(line, line_number) else: print(line, end='') except FileNotFoundError: print(f"cat: {filepath}: No such file or directory") + return line_number def main(): parser = argparse.ArgumentParser(description="Concatenate files and print on the standard output.") @@ -32,13 +35,6 @@ def main(): parser.add_argument('files', nargs='+', help='files to concatenate') args = parser.parse_args() - files = [] - for pattern in args.files: - files.extend(glob.glob(pattern) or [pattern]) - - line_counter = [1] - - # Determine numbering mode (mutually exclusive) if args.n and args.b: parser.error("options -n and -b are mutually exclusive") elif args.n: @@ -48,8 +44,9 @@ def main(): else: numbering = Numbering.NONE - for file in sorted(files): - cat(file, numbering=numbering, line_counter=line_counter) + line_number = 1 # start line numbering + for file in args.files: + line_number = cat(file, numbering=numbering, start_line=line_number) if __name__ == "__main__": main() diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index d7499b3a..bd308276 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -1,7 +1,8 @@ import os import argparse -def ls(path='.', one_column=False, show_hidden=False): +def ls(path, one_column, show_hidden): + """List files in a directory, optionally in one column or including hidden files.""" try: files = os.listdir(path) if not show_hidden: @@ -14,6 +15,8 @@ def ls(path='.', one_column=False, show_hidden=False): print(*files) except FileNotFoundError: print(f"ls: cannot access '{path}': No such file or directory") + except NotADirectoryError: + print(f"ls: cannot access '{path}': Not a directory") def main(): parser = argparse.ArgumentParser() @@ -25,4 +28,4 @@ def main(): ls(args.path, args.one_column, args.a) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index a658c87f..23de6599 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -1,6 +1,7 @@ import argparse def wc(path, count_lines, count_words, count_bytes): + """Count lines, words, and bytes for a single file.""" try: with open(path, 'r') as f: content = f.read() @@ -8,19 +9,26 @@ def wc(path, count_lines, count_words, count_bytes): words = content.split() bytes_ = len(content.encode('utf-8')) + # Determine what to show + if not any([count_lines, count_words, count_bytes]): + count_lines = count_words = count_bytes = True + parts = [] if count_lines: parts.append(str(len(lines))) if count_words: parts.append(str(len(words))) if count_bytes: parts.append(str(bytes_)) - if not parts: - parts = [str(len(lines)), str(len(words)), str(bytes_)] print(' '.join(parts), path) + return (len(lines) if count_lines else 0, + len(words) if count_words else 0, + bytes_ if count_bytes else 0) except FileNotFoundError: print(f"wc: {path}: No such file or directory") + return (0, 0, 0) except IsADirectoryError: print(f"wc: {path}: Is a directory") + return (0, 0, 0) def main(): parser = argparse.ArgumentParser() @@ -30,8 +38,21 @@ def main(): parser.add_argument('paths', nargs='+', help='Files to count') args = parser.parse_args() + total_lines = total_words = total_bytes = 0 + multiple_files = len(args.paths) > 1 + for path in args.paths: - wc(path, args.l, args.w, args.c) + l, w, b = wc(path, args.l, args.w, args.c) + total_lines += l + total_words += w + total_bytes += b + + if multiple_files: + parts = [] + if args.l or not any([args.l, args.w, args.c]): parts.append(str(total_lines)) + if args.w or not any([args.l, args.w, args.c]): parts.append(str(total_words)) + if args.c or not any([args.l, args.w, args.c]): parts.append(str(total_bytes)) + print(' '.join(parts), 'total') if __name__ == "__main__": - main() \ No newline at end of file + main()