Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions implement-shell-tools/cat/cat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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="")


def cat(filepath, numbering, start_line):
line_number = start_line

try:
with open(filepath) as file:
for line in file:
should_number = (
numbering == Numbering.ALL
or (
numbering == Numbering.NONEMPTY
and line.strip("\n")
)
)

if should_number:
print_numbered_line(line, line_number)
line_number += 1
else:
print(line, end="")

except FileNotFoundError:
print(
f"cat: {filepath}: No such file or directory",
file=sys.stderr,
)
return line_number, False

except IsADirectoryError:
print(
f"cat: {filepath}: Is a directory",
file=sys.stderr,
)
return line_number, False

except PermissionError:
print(
f"cat: {filepath}: Permission denied",
file=sys.stderr,
)
return line_number, False

return line_number, True


def main():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you test this implementation?

I created two files and tried using cat -n /file/1 /file/2 and cat -b /file/1 /file/2 and compared the output with using your script, and didn't always get the same results

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment still stands - I get different results between your program and the builtin cat.

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
success = True

for filepath in args.files:
line_number, file_success = cat(
filepath,
numbering=numbering,
start_line=line_number,
)
success = success and file_success

return 0 if success else 1


if __name__ == "__main__":
sys.exit(main())
82 changes: 82 additions & 0 deletions implement-shell-tools/ls/ls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import argparse
import os
import sys


def ls(path, one_column, show_hidden):
try:
if os.path.isfile(path):
print(os.path.basename(path))
return True

files = os.listdir(path)

if show_hidden:
files = [".", ".."] + files
else:
files = [file for file in files if not file.startswith(".")]

files.sort()

separator = "\n" if one_column else "\t"
print(*files, sep=separator)

return True

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,
)
except PermissionError:
print(
f"ls: cannot open directory '{path}': Permission denied",
file=sys.stderr,
)

return False


def main():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question about testing here - If I run ls -a I get different files listed than if I run python3 ls.py -a.

parser = argparse.ArgumentParser(
description="List directory contents."
)

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()

success = ls(
path=args.path,
one_column=args.one_column,
show_hidden=args.a,
)

return 0 if success else 1


if __name__ == "__main__":
sys.exit(main())
147 changes: 147 additions & 0 deletions implement-shell-tools/wc/wc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import argparse
import sys


def wc(path):
try:
with open(path, "rb") as file:
content = file.read()

line_count = content.count(b"\n")
word_count = len(content.split())
byte_count = len(content)

return line_count, word_count, byte_count

except FileNotFoundError:
print(
f"wc: {path}: No such file or directory",
file=sys.stderr,
)
except IsADirectoryError:
print(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this happens, what will the exit code of the process be? What should it be?

(Same question for FileNotFoundError)

f"wc: {path}: Is a directory",
file=sys.stderr,
)
except PermissionError:
print(
f"wc: {path}: Permission denied",
file=sys.stderr,
)
except OSError as error:
print(
f"wc: {path}: {error}",
file=sys.stderr,
)

return None


def print_stats(
line_count,
word_count,
byte_count,
filename,
show_lines,
show_words,
show_bytes,
):
parts = []

if show_lines:
parts.append(f"{line_count:7d}")

if show_words:
parts.append(f"{word_count:7d}")

if show_bytes:
parts.append(f"{byte_count:7d}")

print("".join(parts), filename)


def main():
parser = argparse.ArgumentParser(
description="Print newline, word, and byte counts for files."
)

parser.add_argument(
"-l",
action="store_true",
help="print the newline count",
)

parser.add_argument(
"-w",
action="store_true",
help="print the word count",
)

parser.add_argument(
"-c",
action="store_true",
help="print the byte count",
)

parser.add_argument(
"paths",
nargs="+",
help="files to count",
)

args = parser.parse_args()

# If no options are supplied, wc prints all three counts.
show_lines = args.l
show_words = args.w
show_bytes = args.c

if not any((show_lines, show_words, show_bytes)):
show_lines = True
show_words = True
show_bytes = True

total_lines = 0
total_words = 0
total_bytes = 0
successful_files = 0

for path in args.paths:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I pass multiple files, the real wc outlines a line that you don't - can you add that too?

counts = wc(path)

if counts is None:
continue

line_count, word_count, byte_count = counts

total_lines += line_count
total_words += word_count
total_bytes += byte_count
successful_files += 1

print_stats(
line_count,
word_count,
byte_count,
path,
show_lines,
show_words,
show_bytes,
)

if len(args.paths) > 1 and successful_files > 0:
print_stats(
total_lines,
total_words,
total_bytes,
"total",
show_lines,
show_words,
show_bytes,
)

return 0 if successful_files == len(args.paths) else 1


if __name__ == "__main__":
sys.exit(main())
Loading