diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 000000000..ee8ec04cb --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,59 @@ +import argparse +import sys +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", + file=sys.stderr + ) + 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 + + 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 000000000..d94d2c48e --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,47 @@ +import os +import sys +import argparse + +def ls(path, one_column, show_hidden): + """List files in a directory, optionally in one column or including hidden files.""" + try: + if os.path.isfile(path): + print(os.path.basename(path)) + return + + files = os.listdir(path) + + if show_hidden: + files = ['.', '..'] + files + else: + 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", + file=sys.stderr + ) + except NotADirectoryError: + print( + f"ls: cannot access '{path}': Not a directory", + file=sys.stderr + ) + +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 000000000..0531b8e42 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,87 @@ +import argparse +import sys + +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() + + line_count = len(lines) + word_count = len(words) + byte_count = len(content.encode('utf-8')) + + if not any([count_lines, count_words, count_bytes]): + count_lines = True + count_words = True + count_bytes = True + + parts = [] + + if count_lines: + parts.append(str(line_count)) + + if count_words: + parts.append(str(word_count)) + + if count_bytes: + parts.append(str(byte_count)) + + print(' '.join(parts), path) + + return line_count, word_count, byte_count + + except FileNotFoundError: + print( + f"wc: {path}: No such file or directory", + file=sys.stderr + ) + return (0, 0, 0) + + except IsADirectoryError: + print( + f"wc: {path}: Is a directory", + file=sys.stderr + ) + 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 = 0 + total_words = 0 + total_bytes = 0 + + multiple_files = len(args.paths) > 1 + show_all = not any([args.l, args.w, args.c]) + + 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 show_all: + parts.append(str(total_lines)) + + if args.w or show_all: + parts.append(str(total_words)) + + if args.c or show_all: + parts.append(str(total_bytes)) + + print(' '.join(parts), 'total') + +if __name__ == "__main__": + main()