Skip to content

Commit 074a085

Browse files
update: cat unix like command
1 parent bb4341c commit 074a085

3 files changed

Lines changed: 402 additions & 59 deletions

File tree

Cat/README.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Python Cat
2+
3+
Author: Nitkarsh Chourasia
4+
5+
A small Python implementation of the Unix `cat` command. It reads text from files or standard input and writes the result to standard output.
6+
7+
## Usage
8+
9+
```powershell
10+
python cat.py [options] [files...]
11+
```
12+
13+
If no files are provided, the program reads from standard input.
14+
15+
## Basic Examples
16+
17+
Read one file:
18+
19+
```powershell
20+
python cat.py text_a.txt
21+
```
22+
23+
Read multiple files in order:
24+
25+
```powershell
26+
python cat.py text_a.txt text_b.txt text_c.txt
27+
```
28+
29+
Read from standard input:
30+
31+
```powershell
32+
Get-Content text_a.txt | python cat.py
33+
```
34+
35+
Use `-` to read standard input between files:
36+
37+
```powershell
38+
Get-Content text_b.txt | python cat.py text_a.txt - text_c.txt
39+
```
40+
41+
## Options
42+
43+
Number all lines:
44+
45+
```powershell
46+
python cat.py -n text_a.txt
47+
```
48+
49+
Number only non-empty lines:
50+
51+
```powershell
52+
python cat.py -b text_a.txt
53+
```
54+
55+
Squeeze repeated blank lines:
56+
57+
```powershell
58+
python cat.py -s text_a.txt
59+
```
60+
61+
Show line endings with `$`:
62+
63+
```powershell
64+
python cat.py -E text_a.txt
65+
```
66+
67+
Combine options:
68+
69+
```powershell
70+
python cat.py -n -E text_a.txt
71+
```
72+
73+
```powershell
74+
python cat.py -b -s text_a.txt text_b.txt
75+
```
76+
77+
## Error Handling
78+
79+
If a file cannot be read, the error is printed to standard error and the program continues with the next file.
80+
81+
```powershell
82+
python cat.py text_a.txt missing.txt text_b.txt
83+
```
84+
85+
The program exits with:
86+
87+
- `0` when all input is processed successfully
88+
- `1` when one or more files cannot be read
89+
90+
## Test Cases
91+
92+
Run the automated test suite:
93+
94+
```powershell
95+
python -m unittest test_cat.py
96+
```
97+
98+
The tests cover:
99+
100+
- reading one file
101+
- reading multiple files in order
102+
- reading from standard input
103+
- using `-` for standard input between files
104+
- continuing after a missing file
105+
- `-n` line numbering
106+
- `-b` non-empty line numbering
107+
- `-b` priority over `-n`
108+
- `-s` repeated blank-line squeezing
109+
- `-E` visible line endings
110+
- combined options
111+
112+
## Design Notes
113+
114+
- Files are processed one at a time instead of loading everything into memory.
115+
- `sys.stdin` and opened files are both treated as streams.
116+
- Line numbering continues across multiple files.
117+
- `-b` takes priority over `-n` when both are used.

Cat/cat.py

Lines changed: 142 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,148 @@
1-
"""
2-
The 'cat' Program Implemented in Python 3
3-
4-
The Unix 'cat' utility reads the contents
5-
of file(s) specified through stdin and 'conCATenates'
6-
into stdout. If it is run without any filename(s) given,
7-
then the program reads from standard input itself,
8-
which means it simply copies stdin to stdout.
9-
10-
It is fairly easy to implement such a program
11-
in Python, and as a result countless examples
12-
exist online. This particular implementation
13-
focuses on the basic functionality of the cat
14-
utility. Compatible with Python 3.6 or higher.
15-
16-
Syntax:
17-
python3 cat.py [filename1] [filename2] etc...
18-
Separate filenames with spaces.
19-
20-
David Costell (DontEatThemCookies on GitHub)
21-
v2 - 03/12/2022
22-
"""
23-
24-
import sys
25-
26-
27-
def with_files(files):
28-
"""Executes when file(s) is/are specified."""
29-
try:
30-
# Read each file's contents and store them
31-
file_contents = [contents for contents in [open(file).read() for file in files]]
32-
except OSError as err:
33-
# This executes when there's an error (e.g. FileNotFoundError)
34-
exit(print(f"cat: error reading files ({err})"))
35-
36-
# Write all file contents into the standard output stream
37-
for contents in file_contents:
38-
sys.stdout.write(contents)
39-
40-
41-
def no_files():
42-
"""Executes when no file(s) is/are specified."""
43-
try:
44-
# Get input, output the input, repeat
45-
while True:
46-
print(input())
47-
# Graceful exit for Ctrl + C, Ctrl + D
48-
except KeyboardInterrupt:
49-
exit()
50-
# exit when no data found in file
51-
except EOFError:
52-
exit()
1+
"""
2+
A simple Python implementation of the Unix cat command.
3+
4+
Author:
5+
- Nitkarsh Chourasia
6+
7+
Features:
8+
- Reads one or more files
9+
- Reads from stdin when no files are given
10+
- Supports "-" as stdin
11+
- Prints errors to stderr
12+
- Continues after file errors
13+
- Uses proper exit codes
14+
- Supports:
15+
-n : number all lines
16+
-b : number non-empty lines
17+
-s : squeeze repeated blank lines
18+
-E : show $ at end of each line
19+
20+
Design notes:
21+
- Files and stdin are both handled as streams.
22+
- Line numbering state is shared across files, matching cat-style behavior.
23+
- File errors are reported to stderr while processing continues.
24+
"""
25+
26+
import argparse
27+
import sys
28+
29+
__author__ = "Nitkarsh Chourasia"
30+
31+
32+
def process_stream(stream, args, state):
33+
"""Read from a stream and write processed output to stdout."""
34+
for line in stream:
35+
is_blank = line == "\n"
36+
37+
if args.squeeze_blank and is_blank and state["previous_was_blank"]:
38+
continue
39+
40+
state["previous_was_blank"] = is_blank
41+
42+
if args.show_ends:
43+
if line.endswith("\n"):
44+
line = line[:-1] + "$\n"
45+
else:
46+
line = line + "$"
47+
48+
if args.number_nonblank:
49+
if not is_blank:
50+
sys.stdout.write(f"{state['line_number']:6}\t")
51+
state["line_number"] += 1
52+
elif args.number:
53+
sys.stdout.write(f"{state['line_number']:6}\t")
54+
state["line_number"] += 1
55+
56+
sys.stdout.write(line)
57+
58+
59+
def process_file(filename, args, state):
60+
"""Open one file and process its contents."""
61+
with open(filename, "r", encoding="utf-8") as file:
62+
process_stream(file, args, state)
63+
64+
65+
def process_files(files, args):
66+
"""Process all given filenames."""
67+
had_error = False
68+
69+
state = {
70+
"line_number": 1,
71+
"previous_was_blank": False,
72+
}
73+
74+
for filename in files:
75+
try:
76+
if filename == "-":
77+
process_stream(sys.stdin, args, state)
78+
else:
79+
process_file(filename, args, state)
80+
except OSError as err:
81+
print(f"cat: {filename}: {err}", file=sys.stderr)
82+
had_error = True
83+
84+
return had_error
85+
86+
87+
def parse_arguments():
88+
"""Parse command-line arguments."""
89+
parser = argparse.ArgumentParser(description="A simple Python cat command.")
90+
91+
parser.add_argument(
92+
"files",
93+
nargs="*",
94+
help="Files to read. Use '-' to read from standard input.",
95+
)
96+
97+
parser.add_argument(
98+
"-n",
99+
"--number",
100+
action="store_true",
101+
help="Number all output lines.",
102+
)
103+
104+
parser.add_argument(
105+
"-b",
106+
"--number-nonblank",
107+
action="store_true",
108+
help="Number non-empty output lines.",
109+
)
110+
111+
parser.add_argument(
112+
"-s",
113+
"--squeeze-blank",
114+
action="store_true",
115+
help="Suppress repeated empty output lines.",
116+
)
117+
118+
parser.add_argument(
119+
"-E",
120+
"--show-ends",
121+
action="store_true",
122+
help="Display $ at the end of each line.",
123+
)
124+
125+
return parser.parse_args()
53126

54127

55128
def main():
56-
"""Entry point of the cat program."""
57-
# Read the arguments passed to the program
58-
if not sys.argv[1:]:
59-
no_files()
60-
else:
61-
with_files(sys.argv[1:])
129+
args = parse_arguments()
130+
131+
if not args.files:
132+
state = {
133+
"line_number": 1,
134+
"previous_was_blank": False,
135+
}
136+
process_stream(sys.stdin, args, state)
137+
sys.exit(0)
138+
139+
had_error = process_files(args.files, args)
140+
141+
if had_error:
142+
sys.exit(1)
143+
144+
sys.exit(0)
62145

63146

64147
if __name__ == "__main__":
65-
main()
148+
main()

0 commit comments

Comments
 (0)