-
-
Notifications
You must be signed in to change notification settings - Fork 42
ZA | 25-SDC-July | Luke Manyamazi | Sprint 4 | Python Implement Shell Tools Exercises #148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
ZA | 25-SDC-July | Luke Manyamazi | Sprint 4 | Python Implement Shell Tools Exercises #148
Conversation
illicitonion
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is generally looking good, but I left a few things to think about :)
implement-shell-tools/cat/cat.py
Outdated
| import glob | ||
| import argparse | ||
|
|
||
| def cat(filepath, n=False, b=False, line_counter=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
n and b are both:
- Not very expressive names
- Seem mutually exclusive - it doesn't make sense for someone to pass both.
Can you think of a way of passing this information into the function which is easier to read, and makes more clear that only one of these should be set?
implement-shell-tools/cat/cat.py
Outdated
| else: | ||
| print(line, end='') | ||
| elif n: | ||
| print(f"{line_counter[0]:6}\t{line}", end='') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's some repetition here between the b case and n case - imagine we wanted to start padding the line number by 8 instead of 6 characters - we'd need to change that in two places. Can you think how to avoid that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can create a single helper function for printing a numbered line. This avoids duplicating the f"{line_counter[0]:6}" formatting. Then in cat(), both the “all lines” and “non-empty lines” cases can just call this helper.
implement-shell-tools/cat/cat.py
Outdated
|
|
||
| files = [] | ||
| for pattern in args.files: | ||
| files.extend(glob.glob(pattern) or [pattern]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's interesting you're globbing here - I would expect a shell to handle this. What would break if you removed the glob expansion yourself?
implement-shell-tools/cat/cat.py
Outdated
| if b: | ||
| if line.strip(): | ||
| print(f"{line_counter[0]:6}\t{line}", end='') | ||
| line_counter[0] += 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this value in an array? You always seem to look at exactly one value in the array?
implement-shell-tools/cat/cat.py
Outdated
| import glob | ||
| import argparse | ||
|
|
||
| def cat(filepath, n=False, b=False, line_counter=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like you always pass values for these arguments - why are you supplying default values for them? Particularly for line_counter where if None is used your code will actually break.
| except FileNotFoundError: | ||
| print(f"cat: {filepath}: No such file or directory") | ||
|
|
||
| def main(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How did you test this implementation?
I created two files and tried using cat -n /file/1 /file/2 and cat -b /file/1 /file/2 and compared the output with using your script, and didn't always get the same results
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment still stands - I get different results between your program and the builtin cat.
implement-shell-tools/ls/ls.py
Outdated
| import os | ||
| import argparse | ||
|
|
||
| def ls(path='.', one_column=False, show_hidden=False): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question as above about default args
implement-shell-tools/wc/wc.py
Outdated
| if count_words: parts.append(str(len(words))) | ||
| if count_bytes: parts.append(str(bytes_)) | ||
|
|
||
| if not parts: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works, but it feels like it's testing for a side-effect ("We didn't add any paths") rather than the cause ("you didn't ask for any of lines, words, or bytes") - I'd maybe rewrite this if to be framed in what the user requested.
(But the code works fine as it is, too)
| parser.add_argument('paths', nargs='+', help='Files to count') | ||
| args = parser.parse_args() | ||
|
|
||
| for path in args.paths: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I pass multiple files, the real wc outlines a line that you don't - can you add that too?
35a4349 to
ff86f10
Compare
…for all the files
|
cat.py Removed the mutable list [1] hack for line numbering; now cat() returns the updated line number. ls.py wc.py |
|
Hi @illicitonion, please kindly review and see if all the requirements were met. If so please mark the PR as complete. Thank you. |
illicitonion
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is generally looking good, but there are a bunch of small details and consistencies to look at :)
| else: | ||
| print(line, end='') | ||
| except FileNotFoundError: | ||
| print(f"cat: {filepath}: No such file or directory") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this print to stdout or stderr? Which should it print to? Why?
| except FileNotFoundError: | ||
| print(f"cat: {filepath}: No such file or directory") | ||
|
|
||
| def main(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment still stands - I get different results between your program and the builtin cat.
| except NotADirectoryError: | ||
| print(f"ls: cannot access '{path}': Not a directory") | ||
|
|
||
| def main(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question about testing here - If I run ls -a I get different files listed than if I run python3 ls.py -a.
| print(f"ls: cannot access '{path}': No such file or directory") | ||
| except NotADirectoryError: | ||
| print(f"ls: cannot access '{path}': Not a directory") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question about stdout vs stderr
| print(f"ls: cannot access '{path}': No such file or directory") | ||
| except NotADirectoryError: | ||
| print(f"ls: cannot access '{path}': Not a directory") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stretch goal: What happens if you run ls /some/file for a file not a directory? What does your program do?
| parser.add_argument('paths', nargs='+', help='Files to count') | ||
| args = parser.parse_args() | ||
|
|
||
| total_lines = total_words = total_bytes = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's generally pretty unusual to initialise multiple variables like this in one go, because it makes it harder to skim the code to see where a particular variable was set/initialised than initialising each on its own line.
|
|
||
| if multiple_files: | ||
| parts = [] | ||
| if args.l or not any([args.l, args.w, args.c]): parts.append(str(total_lines)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're repeating this not any([args.l, args.w, args.c]) three times - can you think of a way to avoid that duplication?
|
|
||
| # Determine what to show | ||
| if not any([count_lines, count_words, count_bytes]): | ||
| count_lines = count_words = count_bytes = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I'd generally avoid putting this all on one line
|
|
||
| print(' '.join(parts), path) | ||
|
|
||
| return (len(lines) if count_lines else 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's interesting that you're doing this filtering twice - you're checking whether we should output lines both inside this function and when printing the totals.
Personally, I would probably just always return the values here, and let the caller decide whether to show them.
| if count_lines: parts.append(str(len(lines))) | ||
| if count_words: parts.append(str(len(words))) | ||
| if count_bytes: parts.append(str(bytes_)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a little unusual that two of these have len calls and the last doesn't - not a big deal, but a bit oddly inconsistent - I would maybe make these consistent.
Learners, PR Template
Self checklist
Changelist
This PR implements basic versions of the following Unix commands in Python:
These scripts were designed to mimic standard command-line tools and handle typical edge cases like missing files and glob patterns.
Questions