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
62 changes: 62 additions & 0 deletions implement-shell-tools/cat/cat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import argparse
import sys
import glob # to find file paths and match patterns



def parse_args():
parser = argparse.ArgumentParser(
prog = "cat",
description= "copying cat commands behavior with -n and -b flags"
)
parser.add_argument("-n", "--number", action="store_true", help="Number all output lines") #with store true argument behaves like a boolean, flag present returns true, not present = false.
parser.add_argument("-b", "--number-nonblank", action="store_true", help="Number non-empty output lines")
parser.add_argument("files", nargs="+", help="Files to read (supports shell globs)") # accepts one or more files names or glob patterns.
return parser.parse_args()

#Uses glob to expand patterns like *.txt.
def expand_files(patterns):
expanded = []
for p in patterns:
matches = glob.glob(p)
if matches:
expanded.extend(sorted(matches)) # predictable order
else:
print(f"cat: {p}: No such file", file=sys.stderr) #Prints an error if a pattern matches nothing.
return expanded

def cat_file(filename, number_all=False, number_nonempty=False):
try:
with open(filename, 'r') as f:
lines = f.readlines()
except FileNotFoundError:
print(f"cat: {filename}: No such file")
return
#Applies numbering rules depending on flags.
line_num = 1
for line in lines:
line = line.rstrip('\n')
if number_all:
print(f"{line_num:6} {line}")
line_num += 1
elif number_nonempty:
if line.strip():
print(f"{line_num:6} {line}")
line_num+= 1
else:
print(line)
else:
print(line)

def main():
args = parse_args() #return an args obj
files = expand_files(args.files) #list of filenames or patterns user typed passed to expand_files helper that uses glob module to expand patterns to actual file paths
if not files:
sys.exit(1) #programs exits with status code 1- signals and error if no files were found

#looping through each file path on expanded list
for file in files:
cat_file(file, number_all=args.number, number_nonempty=args.number_nonblank)

if __name__ == "__main__":
main()
56 changes: 56 additions & 0 deletions implement-shell-tools/ls/ls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import argparse
import os #interacting with the filesystem (paths, directories)
import sys #access to system streams (stdout, stderr) and arguments

def parseargs():
parser = argparse.ArgumentParser(
prog = "ls",
description = "ls clone to -1 and -a flags"
)
parser.add_argument("-1", dest = "one_per_line", action = "store_true",help = "List one file per line") #dest sets the attribute name in parsed argument.
parser.add_argument("-a", "--all", action ="store_true",help = "Include directory entries whose names begin with a dot (.)")
parser.add_argument("dirs", nargs = "*", default = ["."], help = "Directories to list (default is current directory)")
return parser.parse_args()

def print_directory_entries(path, one_per_line=False, show_all=False):
try:
# Retrieve all entries (files and subdirectories) inside the given path
directory_entries = os.listdir(path)
except FileNotFoundError:
# If the directory doesn't exist, print an error to stderr
print(f"ls: cannot access '{path}': No such file or directory", file=sys.stderr)
return
except PermissionError:
# If the directory exists but we don't have permission, print an error
print(f"ls: cannot open directory '{path}': Permission denied", file=sys.stderr)
return

# By default, ls hides "dotfiles" (names starting with ".").
# Only include them if the -a flag (show_all=True) is set.
if show_all:
directory_entries.extend([".",".."])
else:
#Filter out hidden files
directory_entries = [entry for entry in directory_entries if not entry.startwith(".")]
#output consistent with the default ls behavior.
directory_entries.sort()

if one_per_line:
# If -1 flag is set, print each entry on its own line
for entry_name in directory_entries:
print(entry_name)
else:
# Default behavior: print entries space-separated on one line
print(" ".join(directory_entries))

def main():
args = parseargs()
# Loop through each directory provided
for directory in args.dirs:
print_directory_entries(
directory,
one_per_line = args.one_per_line,
show_all = args.all
)
if __name__ == "__main__":
main()
82 changes: 82 additions & 0 deletions implement-shell-tools/wc/wc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import argparse
import sys
import glob # to find file paths and match patterns

def parseargs():
parser = argparse.ArgumentParser(
prog = "wc",
description = "wc clone supporting -l, -w, -c flags"
)
parser.add_argument("-l", "--lines", action = "store_true",help = "print line counts")
parser.add_argument("-w", "--words", action ="store_true",help = "print word count")
parser.add_argument("-c", "--bytes", action ="store_true", help = "print byte count")
parser.add_argument("files", nargs="+", help="Files to read (shell globs allowed)")

return parser.parse_args()

#Uses glob to expand patterns like *.txt.
def expand_files(patterns):
expanded = []
for p in patterns:
matches = glob.glob(p)
if matches:
expanded.extend(sorted(matches)) # predictable order
else:
print(f"wc: {p}: No such file", file=sys.stderr) #Prints an error if a pattern matches nothing.
return expanded

def file_counts(filename):
try:
with open(filename, "rb") as f: #opening file in binary mode to be able to count bytes.
data = f.read()
except FileNotFoundError:
print(f"wc: {filename}: No such file", file=sys.stderr)
return None

text = data.decode("utf-8", errors="ignore")
line_count = text.count("\n")
word_count = len(text.split())
byte_count = len(data)

return line_count, word_count, byte_count

def print_counts(filename, counts, show_lines, show_words, show_bytes): #args.lines is passed into the parameter show_lines and same for other arguments.
line_count, word_count, byte_count = counts

# If no flags are set, show all three
if not (show_lines or show_words or show_bytes):
print(f"{line_count:7} {word_count:7} {byte_count:7} {filename}")
return

output = []
if show_lines:
output.append(f"{line_count:7}") #:7 to ensure nums are rightaligned in a spaced column -just like GNU wc
if show_words:
output.append(f"{word_count:7}")
if show_bytes:
output.append(f"{byte_count:7}")
output.append(filename)
print(" ".join(output))

def main():
args = parseargs()
files = expand_files(args.files)

total_lines, total_words, total_bytes = 0, 0, 0

for file in files:
counts = file_counts(file)
if counts is None:
continue
print_counts(file, counts, args.lines, args.words, args.bytes)
total_lines += counts[0]
total_words += counts[1]
total_bytes += counts[2]

# If multiple files, print totals
if len(files) > 1:
total_counts = (total_lines, total_words, total_bytes)
print_counts("total", total_counts, args.lines, args.words, args.bytes)

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