Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 +1,2 @@
node_modules
.venv/
52 changes: 52 additions & 0 deletions implement-shell-tools/cat/my_cat.py
Comment thread
LonMcGregor marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import argparse
import sys

parser = argparse.ArgumentParser(
description="Reads and prints one or more files, optionally numbering lines continuously"
)
parser.add_argument("paths", nargs='+', help="One or more file paths")
parser.add_argument("-n", "--number", action="store_true", help="Number all output lines")
parser.add_argument("-b", "--number-nonblank", action="store_true", help="Number non-empty output lines")

args = parser.parse_args()

file_paths = args.paths
number_nonblank = args.number_nonblank
number_all = args.number and not number_nonblank

line_number = 1

for path in file_paths:
try:
with open(path, 'r', encoding='utf-8') as f:
lines = f.readlines()

if number_nonblank:
nonblank_lines = [line for line in lines if line.strip()]
max_digits = len(str(len(nonblank_lines)))

for line in lines:
if line.strip() == "":
print()
else:
num_str = str(line_number).rjust(max_digits)
print(f"{num_str}\t{line.rstrip()}")
line_number += 1

elif number_all:
max_digits = len(str(len(lines) * len(file_paths)))

for line in lines:
num_str = str(line_number).rjust(max_digits)
print(f"{num_str}\t{line.rstrip()}")
line_number += 1

else:
for line in lines:
print(line, end='')
if not lines[-1].endswith('\n'):
print()

except Exception as e:
print(f'Error reading file "{path}": {e}', file=sys.stderr)
sys.exit(1)
53 changes: 53 additions & 0 deletions implement-shell-tools/ls/my_ls.py
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is a very good implementation, but I think you might have misread -1 in the readme as -l. You can keep -l, which is a good stretch task, but can you add the implementation for -1 as well?

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import os
import sys
import stat
import argparse

parser = argparse.ArgumentParser(
description="List files in a directory (simplified ls implementation)"
)
parser.add_argument("paths", nargs="*", default=["."], help="One or more file or directory paths")
parser.add_argument("-l", "--longList", action="store_true", help="Long listing format")
parser.add_argument("-a", "--all", action="store_true", help="Include hidden files")

args = parser.parse_args()

file_paths = args.paths
show_long = args.longList
show_all = args.all

def format_permissions(mode):
return stat.filemode(mode)

for input_path in file_paths:
try:
if not os.path.exists(input_path):
raise FileNotFoundError(f'No such file or directory: {input_path}')

if os.path.isfile(input_path):
if show_long:
file_stat = os.stat(input_path)
perms = format_permissions(file_stat.st_mode)
size = str(file_stat.st_size).rjust(6)
print(f"{perms} {size} {input_path}")
else:
print(input_path)

elif os.path.isdir(input_path):
entries = os.listdir(input_path)
if not show_all:
entries = [e for e in entries if not e.startswith(".")]

for entry in entries:
full_path = os.path.join(input_path, entry)
entry_stat = os.stat(full_path)
if show_long:
perms = format_permissions(entry_stat.st_mode)
size = str(entry_stat.st_size).rjust(6)
print(f"{perms} {size} {entry}")
else:
print(entry)

except Exception as e:
print(f'Error reading "{input_path}": {e}', file=sys.stderr)
sys.exit(1)
78 changes: 78 additions & 0 deletions implement-shell-tools/wc/my_wc.py
Comment thread
LonMcGregor marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import os
import sys
import argparse

# CLI argument parsing
parser = argparse.ArgumentParser(description="Simplified implementation of wc")
parser.add_argument("paths", nargs="*", default=["."], help="One or more file or directory paths")
parser.add_argument("-l", "--line", action="store_true", help="Count lines")
parser.add_argument("-w", "--word", action="store_true", help="Count words")
parser.add_argument("-c", "--character", action="store_true", help="Count characters")

args = parser.parse_args()
file_paths = args.paths

# Fallback: if no options passed, show all
show_line = args.line
show_word = args.word
show_char = args.character
show_all = not (show_line or show_word or show_char)

# Count content in a string
def count_content(content):
lines = content.splitlines()
words = content.strip().split()
characters = len(content)
return len(lines), len(words), characters

# Totals for multiple files
total = {
"lines": 0,
"words": 0,
"characters": 0
}

file_count = 0

for input_path in file_paths:
try:
if os.path.isdir(input_path):
print(f"{input_path} is a directory. Skipping.")
continue

with open(input_path, "r", encoding="utf-8") as f:
content = f.read()

lines, words, characters = count_content(content)

total["lines"] += lines
total["words"] += words
total["characters"] += characters
file_count += 1

# Prepare output per file
output_parts = []
if show_line or show_all:
output_parts.append(f"{lines:8}")
if show_word or show_all:
output_parts.append(f"{words:8}")
if show_char or show_all:
output_parts.append(f"{characters:8}")

output_parts.append(input_path)
print(" ".join(output_parts))

except Exception as e:
print(f'Error reading "{input_path}": {e}', file=sys.stderr)

# Print totals if more than one file processed
if file_count > 1:
output_parts = []
if show_line or show_all:
output_parts.append(f"{total['lines']:8}")
if show_word or show_all:
output_parts.append(f"{total['words']:8}")
if show_char or show_all:
output_parts.append(f"{total['characters']:8}")
output_parts.append("total")
print(" ".join(output_parts))