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
54 changes: 54 additions & 0 deletions implement-shell-tools/cat/script-02.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
import sys
import argparse

def read_file(path, number_all, number_nonblank, line_counter):
"""Reads a file and prints its contents with the requested numbering."""
try:
with open(path, "r") as f:
for line in f:
line_no_newline = line.rstrip("\n")

if number_all:
print(f"{line_counter:6}\t{line_no_newline}")
line_counter += 1

elif number_nonblank:
if line_no_newline.strip():
print(f"{line_counter:6}\t{line_no_newline}")
line_counter += 1
else:
print("") # blank line stays blank

else:
print(line_no_newline)

return line_counter

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


def main():
parser = argparse.ArgumentParser(description="Simple cat implementation")
parser.add_argument("-n", action="store_true", help="number all lines")
parser.add_argument("-b", action="store_true", help="number non-blank lines")
parser.add_argument("files", nargs="+", help="files to read")

args = parser.parse_args()

# cat rule: if -b is used, ignore -n
number_all = args.n
number_nonblank = args.b
if number_nonblank:
number_all = False

line_counter = 1

for path in args.files:
line_counter = read_file(path, number_all, number_nonblank, line_counter)


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

def read_file(path, number_all, number_nonblank, line_counter):
"""Reads a file and prints its contents with the requested numbering."""
try:
with open(path, "r") as f:
for line in f:
line_no_newline = line.rstrip("\n")

if number_all:
print(f"{line_counter:6}\t{line_no_newline}")
line_counter += 1

elif number_nonblank:
if line_no_newline.strip():
print(f"{line_counter:6}\t{line_no_newline}")
line_counter += 1
else:
print("") # blank line stays blank

else:
print(line_no_newline)

return line_counter

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


def main():
parser = argparse.ArgumentParser(description="Simple cat implementation")
parser.add_argument("-n", action="store_true", help="number all lines")
parser.add_argument("-b", action="store_true", help="number non-blank lines")
parser.add_argument("files", nargs="+", help="files to read")

args = parser.parse_args()

# cat rule: if -b is used, ignore -n
number_all = args.n
number_nonblank = args.b
if number_nonblank:
number_all = False

line_counter = 1

for path in args.files:
line_counter = read_file(path, number_all, number_nonblank, line_counter)


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

import os
import sys
import argparse


def list_directory(path, show_all):
"""List contents of a directory, respecting the -a and -1 options."""

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

# If -a is not provided, hide dotfiles
if not show_all:
entries = [e for e in entries if not e.startswith(".")]

# Sort alphabetically like ls normally does
entries.sort()

# Print one entry per line (-1 behaviour)
for entry in entries:
print(entry)


def main():
parser = argparse.ArgumentParser(description="Simple ls implementation")
parser.add_argument(
"-a",
action="store_true",
help="include directory entries whose names begin with a dot",
)
parser.add_argument(
"-1",
dest="one_per_line",
action="store_true",
help="list one file per line",
)
parser.add_argument("path", nargs="?", default=".", help="directory to list")

args = parser.parse_args()

# Only -1 is supported, but it's always required in this assignment
if not args.one_per_line:

Choose a reason for hiding this comment

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

Why is this required? is there a reason i should not output the files on the same line?

Copy link
Author

Choose a reason for hiding this comment

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

There is no technical reason to require -1.
The program already outputs one file per line regardless, so enforcing -1 is unnecessary.
I added the check because I thought the assignment required explicitly using -1, but in practice the program should either: treat -1 as the default behaviour, or Ignore it entirely since only one output format is implemented.

Choose a reason for hiding this comment

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

If you re-read the readme for the ls task, can you see where it is asking for -1 to be implemented? You need to be able to test with it on and off. Right now, every time it runs it prints each file on a new line, so even though you removed the option, how will you test if this option is implemented?

Copy link
Author

Choose a reason for hiding this comment

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

You are right and I understand now perfectly the exercise.... I did some changes in the code wehere the program:

-1 changes output behaviour
-a includes dotfiles

  • Clean error handling
  • Easy to extend later (-l, multiple paths, etc.)

I have tested:
./ls.py
./ls.py -1
./ls.py -a
./ls.py -1 -a
./ls.py sample-files
./ls.py -1 -a sample-files

print("This program only supports the -1 option.", file=sys.stderr)
sys.exit(1)

list_directory(args.path, show_all=args.a)


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

import os
import sys
import argparse


def list_directory(path, show_all, one_per_line):
try:
# If path is a file, just print it
if os.path.isfile(path):
print(path)
return

# If path is a directory, list its contents
if os.path.isdir(path):
entries = os.listdir(path)
else:
print(
f"ls: cannot access '{path}': No such file or directory",
file=sys.stderr,
)
return

except PermissionError:
print(
f"ls: cannot open directory '{path}': Permission denied",
file=sys.stderr,
)
return

# Hide dotfiles unless -a is used
if not show_all:
entries = [e for e in entries if not e.startswith(".")]

entries.sort()

# Output formatting
if one_per_line:
for entry in entries:
print(entry)
else:
print(" ".join(entries))


def main():
parser = argparse.ArgumentParser(description="Simple ls implementation")

parser.add_argument(
"-a",
action="store_true",
help="include directory entries whose names begin with a dot",
)

parser.add_argument(
"-1",
dest="one_per_line",
action="store_true",
help="list one file per line",
)

parser.add_argument(
"path",
nargs="?",
default=".",
help="directory or file to list",
)

args = parser.parse_args()

list_directory(
args.path,
show_all=args.a,
one_per_line=args.one_per_line,
)


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


def count_file(path):
"""Return (lines, words, bytes) for the given file."""
try:
with open(path, "rb") as f:
content = f.read()
except FileNotFoundError:
print(f"wc: {path}: No such file or directory", file=sys.stderr)
return None

text = content.decode("utf-8", errors="replace")
lines = text.count("\n")
words = len(text.split())
bytes_ = len(content)

return lines, words, bytes_


def print_result(counts, path, show_l, show_w, show_c):
"""Format and print output for a file."""
lines, words, bytes_ = counts

# If no specific flag is given → print all three (like wc)
if not (show_l or show_w or show_c):
print(f"{lines:>7} {words:>7} {bytes_:>7} {path}")
return

out = []
if show_l:
out.append(f"{lines:>7}")
if show_w:
out.append(f"{words:>7}")
if show_c:
out.append(f"{bytes_:>7}")

print(" ".join(out), path)


def main():
parser = argparse.ArgumentParser(description="Simple wc implementation")
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="file(s) to process")

args = parser.parse_args()

total_lines = total_words = total_bytes = 0
multiple_files = len(args.paths) > 1

for path in args.paths:
result = count_file(path)
if result is None:
continue

lines, words, bytes_ = result
print_result(result, path, args.l, args.w, args.c)

total_lines += lines
total_words += words
total_bytes += bytes_

# Print totals if more than one file
if multiple_files:
if not (args.l or args.w or args.c):
print(f"{total_lines:>7} {total_words:>7} {total_bytes:>7} total")
else:
out = []
if args.l:
out.append(f"{total_lines:>7}")
if args.w:
out.append(f"{total_words:>7}")
if args.c:
out.append(f"{total_bytes:>7}")
print(" ".join(out), "total")


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