From 53fa60024ad7607bc039228406a1aa81b0deeff7 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Sun, 23 Nov 2025 14:44:31 +0000 Subject: [PATCH 01/11] Add prep to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3c3629e6..cc8b5568 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +prep/ From 6a33a5e5f786f565b02cd66b097fad7e55b20d89 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Sun, 23 Nov 2025 19:37:42 +0000 Subject: [PATCH 02/11] Implement basic cat command in Python --- implement-shell-tools/cat/cat.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 implement-shell-tools/cat/cat.py diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 00000000..757c15e0 --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,8 @@ +import sys + +filename = sys.argv[1] + +with open(filename, "r") as f: + content = f.read() + +print(content) From d1c13548d71817726d2972fbedcb8e2e380c938a Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:25:29 +0000 Subject: [PATCH 03/11] Add -n flag to number lines in cat command --- implement-shell-tools/cat/cat.py | 33 ++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 757c15e0..0b4a7943 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -1,8 +1,33 @@ import sys +import argparse -filename = sys.argv[1] +# Setup argument parser +parser = argparse.ArgumentParser( + prog="cat", + description="Concatenate and display files" +) + +parser.add_argument("-n", "--number", action="store_true", help="Number all output lines") +parser.add_argument("path", help="File to read") + +args = parser.parse_args() + + +# Read the file +with open(args.path, "r") as f: + content = f.read() -with open(filename, "r") as f: - content = f.read() -print(content) +# Check if numbering is needed +if args.number: + lines = content.split("\n") + numbered_lines = [] + + for index, line in enumerate(lines): + line_number = index + 1 + numbered_line = f"{line_number:6}\t{line}" # Format with tab like real cat + numbered_lines.append(numbered_line) + + print("\n".join(numbered_lines)) +else: + print(content) From 6607cde2b1ccddbde7c5037d76cbdfd0b682e2d0 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:42:14 +0000 Subject: [PATCH 04/11] Add -b flag to number only non-empty lines --- implement-shell-tools/cat/cat.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 0b4a7943..8f08038a 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -8,11 +8,11 @@ ) 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 lines only") parser.add_argument("path", help="File to read") args = parser.parse_args() - # Read the file with open(args.path, "r") as f: content = f.read() @@ -29,5 +29,21 @@ numbered_lines.append(numbered_line) print("\n".join(numbered_lines)) + +elif args.number_nonblank: + lines = content.split("\n") + numbered_lines = [] + line_number = 0 + + for line in lines: + if line.strip() == "": # Empty line + numbered_lines.append(line) # Don't number it + else: # Non-empty line + line_number = line_number + 1 + numbered_line = f"{line_number:6}\t{line}" + numbered_lines.append(numbered_line) + + print("\n".join(numbered_lines)) + else: print(content) From d1ed6cac71e6c2a935220cac8259d57f294b106d Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Sun, 23 Nov 2025 21:20:46 +0000 Subject: [PATCH 05/11] Add support for multiple files in cat command --- implement-shell-tools/cat/cat.py | 59 ++++++++++++++++---------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 8f08038a..108678d9 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -9,41 +9,42 @@ 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 lines only") -parser.add_argument("path", help="File to read") +parser.add_argument("paths", nargs='+', help="Files to read") args = parser.parse_args() -# Read the file -with open(args.path, "r") as f: - content = f.read() - +line_number = 0 # Shared counter across all files -# Check if numbering is needed -if args.number: - lines = content.split("\n") - numbered_lines = [] +# Process each file +for path in args.paths: # LEVEL 1: for loop starts + with open(path, "r") as f: # LEVEL 2: inside for loop + content = f.read() # LEVEL 3: inside with block - for index, line in enumerate(lines): - line_number = index + 1 - numbered_line = f"{line_number:6}\t{line}" # Format with tab like real cat - numbered_lines.append(numbered_line) - - print("\n".join(numbered_lines)) - -elif args.number_nonblank: - lines = content.split("\n") - numbered_lines = [] - line_number = 0 - - for line in lines: - if line.strip() == "": # Empty line - numbered_lines.append(line) # Don't number it - else: # Non-empty line - line_number = line_number + 1 + # Check if numbering is needed + if args.number: # LEVEL 2: inside for loop + lines = content.split("\n") # LEVEL 3: inside if + numbered_lines = [] + + for index, line in enumerate(lines): # LEVEL 3: inside if + line_number = line_number + 1 # LEVEL 4: inside inner for numbered_line = f"{line_number:6}\t{line}" numbered_lines.append(numbered_line) + + print("\n".join(numbered_lines)) # LEVEL 3: inside if - print("\n".join(numbered_lines)) + elif args.number_nonblank: # LEVEL 2: inside for loop + lines = content.split("\n") # LEVEL 3: inside elif + numbered_lines = [] + + for line in lines: # LEVEL 3: inside elif + if line.strip() == "": # LEVEL 4: inside inner for + numbered_lines.append(line) + else: + line_number = line_number + 1 + numbered_line = f"{line_number:6}\t{line}" + numbered_lines.append(numbered_line) + + print("\n".join(numbered_lines)) -else: - print(content) + else: # LEVEL 2: inside for loop + print(content) # LEVEL 3: inside else \ No newline at end of file From ba64d2860fdca19fa71f457794a1a36f00c85f2e Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Sun, 23 Nov 2025 22:31:34 +0000 Subject: [PATCH 06/11] implement ls-1 command --- implement-shell-tools/ls/ls.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 implement-shell-tools/ls/ls.py diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 00000000..613cd5a4 --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,20 @@ +import os +import argparse + +# Setup argument parser +parser = argparse.ArgumentParser( + prog="ls", + description="Lists contents of a directory" +) + +parser.add_argument("-1", dest="one_column", action="store_true", help="List one file per line") +parser.add_argument("path", nargs='?', default=".", help="Directory to list (default: current directory)") + +args = parser.parse_args() + +# List directory contents +files = os.listdir(args.path) + +# Print each file +for file in files: + print(file) \ No newline at end of file From 37c7132ffae6e6ace1feb1404518dcd516b35017 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Sun, 23 Nov 2025 22:37:23 +0000 Subject: [PATCH 07/11] Add -a flag to ls to show hidden files --- implement-shell-tools/ls/ls.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index 613cd5a4..c99aaea3 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -8,6 +8,7 @@ ) parser.add_argument("-1", dest="one_column", action="store_true", help="List one file per line") +parser.add_argument("-a", "--all", action="store_true", help="Include hidden files") parser.add_argument("path", nargs='?', default=".", help="Directory to list (default: current directory)") args = parser.parse_args() @@ -15,6 +16,9 @@ # List directory contents files = os.listdir(args.path) -# Print each file +# Filter out hidden files unless -a flag is used +if not args.all: + files = [f for f in files if not f.startswith('.')] + for file in files: print(file) \ No newline at end of file From 5d1476bb17005a7e926656daecaf6d5d4817f698 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:32:40 +0000 Subject: [PATCH 08/11] Implement basic wc command in Python --- implement-shell-tools/wc/wc.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 implement-shell-tools/wc/wc.py diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 00000000..6d534920 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,22 @@ +import argparse + +# Setup argument parser +parser = argparse.ArgumentParser( + prog="wc", + description="Count lines, words, and characters" +) +parser.add_argument("paths", nargs='+', help="Files to count") + +args = parser.parse_args() + +for path in args.paths: + # Read the file + with open(path, "r") as f: + content = f.read() + + # Count lines, words, characters + lines = len(content.rstrip('\n').split('\n')) + words = len(content.split()) + chars = len(content) + + print(f"{lines:8}{words:8}{chars:8} {path}") \ No newline at end of file From df5b8e52d4de7c1ff58b14ccea03e1dd7754b7c1 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:36:30 +0000 Subject: [PATCH 09/11] Implement wc command with -l, -w, and -c flags --- implement-shell-tools/wc/wc.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index 6d534920..329e4512 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -6,6 +6,9 @@ description="Count lines, words, and characters" ) parser.add_argument("paths", nargs='+', help="Files to count") +parser.add_argument("-l", "--lines", action="store_true", help="Count lines only") +parser.add_argument("-w", "--words", action="store_true", help="Count words only") +parser.add_argument("-c", "--chars", action="store_true", help="Count characters only") args = parser.parse_args() @@ -19,4 +22,11 @@ words = len(content.split()) chars = len(content) - print(f"{lines:8}{words:8}{chars:8} {path}") \ No newline at end of file + if args.lines: + print(f"{lines:8} {path}") + elif args.words: + print(f"{words:8} {path}") + elif args.chars: + print(f"{chars:8} {path}") + else: + print(f"{lines:8}{words:8}{chars:8} {path}") \ No newline at end of file From 36020221416b0290b903d3d22db2d41daffdf335 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:50:33 +0000 Subject: [PATCH 10/11] Add total line for multiple files in wc --- implement-shell-tools/wc/wc.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index 329e4512..f3e00f0a 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -12,6 +12,11 @@ args = parser.parse_args() +# Track totals +total_lines = 0 +total_words = 0 +total_chars = 0 + for path in args.paths: # Read the file with open(path, "r") as f: @@ -22,6 +27,11 @@ words = len(content.split()) chars = len(content) + # Add to totals + total_lines += lines + total_words += words + total_chars += chars + if args.lines: print(f"{lines:8} {path}") elif args.words: @@ -29,4 +39,17 @@ elif args.chars: print(f"{chars:8} {path}") else: - print(f"{lines:8}{words:8}{chars:8} {path}") \ No newline at end of file + print(f"{lines:8}{words:8}{chars:8} {path}") + + +# Print totals if multiple files +if len(args.paths) > 1: + if args.lines: + print(f"{total_lines:8} total") + elif args.words: + print(f"{total_words:8} total") + elif args.chars: + print(f"{total_chars:8} total") + else: + print(f"{total_lines:8}{total_words:8}{total_chars:8} total") + From 3b7cf4b780fa3ea0681359a85e89b167fbfa75a1 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:32:29 +0000 Subject: [PATCH 11/11] Address PR review: reduce duplication and fix flag handling --- implement-shell-tools/cat/cat.py | 51 +++++++++------------- implement-shell-tools/ls/ls.py | 32 ++++++++------ implement-shell-tools/wc/wc.py | 73 +++++++++++++------------------- 3 files changed, 68 insertions(+), 88 deletions(-) diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py index 108678d9..7743c111 100644 --- a/implement-shell-tools/cat/cat.py +++ b/implement-shell-tools/cat/cat.py @@ -13,38 +13,25 @@ args = parser.parse_args() -line_number = 0 # Shared counter across all files +line_number = 1 # Shared counter across all files -# Process each file -for path in args.paths: # LEVEL 1: for loop starts - with open(path, "r") as f: # LEVEL 2: inside for loop - content = f.read() # LEVEL 3: inside with block +for path in args.paths: + with open(path, "r") as f: - # Check if numbering is needed - if args.number: # LEVEL 2: inside for loop - lines = content.split("\n") # LEVEL 3: inside if - numbered_lines = [] - - for index, line in enumerate(lines): # LEVEL 3: inside if - line_number = line_number + 1 # LEVEL 4: inside inner for - numbered_line = f"{line_number:6}\t{line}" - numbered_lines.append(numbered_line) - - print("\n".join(numbered_lines)) # LEVEL 3: inside if - - elif args.number_nonblank: # LEVEL 2: inside for loop - lines = content.split("\n") # LEVEL 3: inside elif - numbered_lines = [] - - for line in lines: # LEVEL 3: inside elif - if line.strip() == "": # LEVEL 4: inside inner for - numbered_lines.append(line) + lines = f.read().splitlines() + + output = [] + for line in lines: + if args.number_nonblank: + if line.strip(): + output.append(f"{line_number:6}\t{line}") + line_number += 1 else: - line_number = line_number + 1 - numbered_line = f"{line_number:6}\t{line}" - numbered_lines.append(numbered_line) - - print("\n".join(numbered_lines)) - - else: # LEVEL 2: inside for loop - print(content) # LEVEL 3: inside else \ No newline at end of file + output.append(line) + elif args.number: + output.append(f"{line_number:6}\t{line}") + line_number += 1 + else: + output.append(line) + + print("\n".join(output)) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py index c99aaea3..e1e301b2 100644 --- a/implement-shell-tools/ls/ls.py +++ b/implement-shell-tools/ls/ls.py @@ -1,24 +1,30 @@ import os import argparse -# Setup argument parser -parser = argparse.ArgumentParser( - prog="ls", - description="Lists contents of a directory" -) +parser = argparse.ArgumentParser(prog="ls", description="Lists contents of a directory") parser.add_argument("-1", dest="one_column", action="store_true", help="List one file per line") parser.add_argument("-a", "--all", action="store_true", help="Include hidden files") -parser.add_argument("path", nargs='?', default=".", help="Directory to list (default: current directory)") +parser.add_argument("paths", nargs='*', default=["."], help="Directories to list") args = parser.parse_args() -# List directory contents -files = os.listdir(args.path) +for path in args.paths: + if len(args.paths) > 1: + print(f"{path}:") -# Filter out hidden files unless -a flag is used -if not args.all: - files = [f for f in files if not f.startswith('.')] + try: + files = os.listdir(path) + if not args.all: + files = [f for f in files if not f.startswith('.')] + + files.sort() -for file in files: - print(file) \ No newline at end of file + sep = "\n" if args.one_column else " " + print(sep.join(files)) + + if len(args.paths) > 1: + print() + + except FileNotFoundError: + print(f"ls: cannot access '{path}': No such file or directory") diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index f3e00f0a..500ac55e 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -1,55 +1,42 @@ import argparse -# Setup argument parser -parser = argparse.ArgumentParser( - prog="wc", - description="Count lines, words, and characters" -) +parser = argparse.ArgumentParser(prog="wc", description="Count lines, words, and characters") parser.add_argument("paths", nargs='+', help="Files to count") -parser.add_argument("-l", "--lines", action="store_true", help="Count lines only") -parser.add_argument("-w", "--words", action="store_true", help="Count words only") -parser.add_argument("-c", "--chars", action="store_true", help="Count characters only") +parser.add_argument("-l", "--lines", action="store_true") +parser.add_argument("-w", "--words", action="store_true") +parser.add_argument("-c", "--chars", action="store_true") args = parser.parse_args() -# Track totals -total_lines = 0 -total_words = 0 -total_chars = 0 +# If no flags are provided, the default behavior is to show all three +show_all = not (args.lines or args.words or args.chars) + +total_stats = [0, 0, 0] # lines, words, chars for path in args.paths: - # Read the file with open(path, "r") as f: content = f.read() - # Count lines, words, characters - lines = len(content.rstrip('\n').split('\n')) - words = len(content.split()) - chars = len(content) - - # Add to totals - total_lines += lines - total_words += words - total_chars += chars - - if args.lines: - print(f"{lines:8} {path}") - elif args.words: - print(f"{words:8} {path}") - elif args.chars: - print(f"{chars:8} {path}") - else: - print(f"{lines:8}{words:8}{chars:8} {path}") - - -# Print totals if multiple files -if len(args.paths) > 1: - if args.lines: - print(f"{total_lines:8} total") - elif args.words: - print(f"{total_words:8} total") - elif args.chars: - print(f"{total_chars:8} total") - else: - print(f"{total_lines:8}{total_words:8}{total_chars:8} total") + stats = [ + len(content.splitlines()), + len(content.split()), + len(content) + ] + + for i in range(3): + total_stats[i] += stats[i] + + output = [] + if args.lines or show_all: output.append(f"{stats[0]:8}") + if args.words or show_all: output.append(f"{stats[1]:8}") + if args.chars or show_all: output.append(f"{stats[2]:8}") + + print(f"{''.join(output)} {path}") +if len(args.paths) > 1: + total_output = [] + if args.lines or show_all: total_output.append(f"{total_stats[0]:8}") + if args.words or show_all: total_output.append(f"{total_stats[1]:8}") + if args.chars or show_all: total_output.append(f"{total_stats[2]:8}") + + print(f"{''.join(total_output)} total") \ No newline at end of file