Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
venv
testoutput.txt
1 change: 1 addition & 0 deletions implement-shell-tools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.venv/
89 changes: 89 additions & 0 deletions implement-shell-tools/cat/mycat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python3

import sys
import glob

def main():
args = sys.argv[1:]

if not args:
print("Usage: cat [-n|-b] file...", file=sys.stderr)
sys.exit(1)

number_all = False
number_nonempty = False
paths = []

for a in args:
if a == "-n":
number_all = True
elif a == "-b":
number_nonempty = True
else:
paths.append(a)

if number_nonempty:
number_all = False

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 kind of relationship where different options/states are related but exclusive can be hard to follow. (Here, it should never be the case that number_all and number_nonempty are both True - it's an invalid state in the program).

Instead of this, we sometimes use enums for this (or can use strings as enums). Consider a single variable number_mode which could be set to either "none", "non_empty" or "all" - here we don't need to think about what both True mean - we just have one variable which could have one of three variables. What do you think about this?

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.

I don't think you addressed this comment?


files = expand_paths(paths)

had_error = print_lines(
files,
number_all=number_all,
number_nonempty=number_nonempty,
)

if had_error:
sys.exit(1)


def expand_paths(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.

You shouldn't need to do this expansion in your program; the shell should do it for you.

"""Expand glob patterns and return sorted unique file list."""
files = []
for p in paths:
matches = glob.glob(p)
if matches:
files.extend(matches)
else:
files.append(p) # keep as-is (will error later if missing)
return sorted(files)

def read_lines(file):
with open(file, "r", encoding="utf-8") as f:
return f.readlines()

def print_lines(files, number_all=False, number_nonempty=False):
had_error = False
line_no = 1
for file in files:
try:
lines = read_lines(file)
except FileNotFoundError:
print(f"cat: {file}: No such file or directory", file=sys.stderr)
Comment thread
illicitonion marked this conversation as resolved.
had_error = True
continue

for line in lines:
is_empty = (line.strip() == "")

if number_nonempty:
if not is_empty:
prefix = f"{line_no:6}\t"
line_no += 1
else:
prefix = ""
elif number_all:
prefix = f"{line_no:6}\t"
line_no += 1
else:
prefix = ""

# avoid double newlines: line already includes '\n'
sys.stdout.write(prefix + line)

return had_error

if __name__ == "__main__":
main()


65 changes: 65 additions & 0 deletions implement-shell-tools/ls/my-ls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3

import sys
import os



def main():
args = sys.argv[1:]

show_all = False
one_per_line = False
paths = []

for a in args:
if a == "-a":
show_all = True
elif a == "-1":
one_per_line = True
else:
paths.append(a)

if not paths:
paths = ["."]

had_error = False

for path in paths:
if list_dir(
path,
show_all=show_all,
one_per_line=one_per_line,
):
had_error = True

if had_error:
sys.exit(1)


def list_dir(path, show_all=False, one_per_line=False):
try:
entries = os.listdir(path)
except FileNotFoundError:
print(f"ls: cannot access '{path}': No such file or directory",
file=sys.stderr,
)
return True

entries = sorted(entries)

if show_all:
normal = sorted([e for e in entries if not e.startswith('.')])
hidden = sorted([e for e in entries if e.startswith('.')])

entries = [".", ".."] + normal + hidden
else:
entries = sorted([e for e in entries if not e.startswith('.')])

for entry in entries:
print(entry)

return False

if __name__ == "__main__":
main()
104 changes: 104 additions & 0 deletions implement-shell-tools/wc/my-wc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3

import sys


def count_file(path):
with open(path, "rb") as f:
content = f.read()

byte_count = len(content)
text = content.decode("utf-8", errors="ignore")

line_count = text.count("\n")
word_count = len(text.split())

return line_count, word_count, byte_count



def main():
args = sys.argv[1:]

show_l = False
show_w = False
show_c = False

paths = []


# parse args
for a in args:
if a == "-l":
show_l = True
elif a == "-w":
show_w = True
elif a == "-c":
show_c = True
else:
paths.append(a)

# default: show all
if not (show_l or show_w or show_c):
show_l = show_w = show_c = True

files = paths

total_l = 0
total_w = 0
total_c = 0

results = []
had_error = False

for file in files:
try:
l, w, c = count_file(file)
except FileNotFoundError:
print(
f"wc: {file}: No such file or directory",
file=sys.stderr,
)
had_error = True
continue

total_l += l
total_w += w
total_c += c

results.append((l,w,c, file))

# print per-file results (GNU-aligned formatting)
for l, w, c, file in results:

parts = []
if show_l:
parts.append(f"{l:3}")
if show_w:
parts.append(f"{w:4}")
if show_c:
parts.append(f"{c:4}")


print("".join(parts) + " " + file)

# print total if multiple files
if len(results) > 1:
parts = []

if show_l:
parts.append(f"{total_l:3}")
if show_w:
parts.append(f"{total_w:4}")
if show_c:
parts.append(f"{total_c:4}")


print("".join(parts) + " total")

if had_error:
sys.exit(1)


if __name__ == "__main__":
main()
Loading