diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 00000000..58dccaee --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,52 @@ +import argparse +from enum import Enum + +class Numbering(Enum): + NONE = 0 + ALL = 1 + NONEMPTY = 2 + +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(): + line_number = print_numbered_line(line, line_number) + else: + print(line, end='') + elif numbering == Numbering.ALL: + 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.") + 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() + + 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 + + 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 new file mode 100644 index 00000000..bd308276 --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,31 @@ +import os +import argparse + +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: + 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") + except NotADirectoryError: + print(f"ls: cannot access '{path}': Not a directory") + +def main(): + parser = argparse.ArgumentParser() + 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.one_column, args.a) + +if __name__ == "__main__": + main() diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 00000000..23de6599 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,58 @@ +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() + lines = content.splitlines() + 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_)) + + 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() + 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() + + total_lines = total_words = total_bytes = 0 + multiple_files = len(args.paths) > 1 + + for path in args.paths: + 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()