Write a program that precisely clones GNU cat version 8.32, which is
installed on my computer.
In any case, if called with the same arguments, same stdin, and same files,
your program's output to both stdout and stderr must by byte-for-byte identical
to that of GNU cat, and the exit code must be the same. This applies to both
valid and invalid commands. It must be impossible to distinguish between GNU
cat and your program without inspecting the file content or analyzing performance.
As usual, arguments starting with hyphens are considered options, except that
-- indicates that arguments after that point are not options. cat -- --help
reads a file literally called --help from the current directory. Options and
non-options may be freely mixed, except no options are allowed after --.
cat has no options that take arguments. Options may be long or short. Long options begin with two hyphens, and must be given
separately. Short options begin with one hyphen and can be combined; cat -v -E -T can be combined into cat -vET with precisely the same effect.
The options are documented in the output of cat --help:
Usage: cat [OPTION]... [FILE]...
Concatenate FILE(s) to standard output.
With no FILE, or when FILE is -, read standard input.
-A, --show-all equivalent to -vET
-b, --number-nonblank number nonempty output lines, overrides -n
-e equivalent to -vE
-E, --show-ends display $ at end of each line
-n, --number number all output lines
-s, --squeeze-blank suppress repeated empty output lines
-t equivalent to -vT
-T, --show-tabs display TAB characters as ^I
-u (ignored)
-v, --show-nonprinting use ^ and M- notation, except for LFD and TAB
--help display this help and exit
--version output version information and exit
Examples:
cat f - g Output f's contents, then standard input, then g's contents.
cat Copy standard input to standard output.
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Full documentation <https://www.gnu.org/software/coreutils/cat>
or available locally via: info '(coreutils) cat invocation'
There are some exceptions to the requirement for a perfect clone. Your program does not have to properly handle file read errors except "file not found" and "permission denied," and it does not need to be internationalized.
Calling GNU cat is cheating.
Because cat 8.32 is not available on all systems, a Python reference implementation is provided. I believe it meets the challenge requirements, although it is entirely ungolfed. Any variation between GNU cat and this program, except as allowed in this challenge, is a bug in this program. You are welcome to use it as the basis of an answer if you would like.
#!/usr/bin/env python3
import sys
import getopt
try:
full_opts, file_names = getopt.gnu_getopt(
sys.argv[1:],
"AbeEnstTuv",
longopts=[
"show-all",
"number-nonblank",
"show-ends",
"number",
"squeeze-blank",
"show-tabs",
"show-nonprinting",
"help",
"version",
],
)
except getopt.GetoptError as e:
invalid_option = str(e)[7:-15]
if invalid_option.startswith("--"):
print(f"cat: unrecognized option '{invalid_option}'", file=sys.stderr)
else:
letter = invalid_option[1]
print(f"cat: invalid option -- '{letter}'", file=sys.stderr)
print("Try 'cat --help' for more information.", file=sys.stderr)
sys.exit(1)
raw_opts = [opt[0] for opt in full_opts]
opts = []
for raw_opt in raw_opts:
if raw_opt in ["-A", "--show-all"]:
opts.append("--show-nonprinting")
opts.append("--show-ends")
opts.append("--show-tabs")
elif raw_opt == "-e":
opts.append("--show-nonprinting")
opts.append("--show-ends")
elif raw_opt == "-t":
opts.append("--show-nonprinting")
opts.append("--show-tabs")
elif raw_opt == "-u":
pass
elif raw_opt.startswith("--"):
opts.append(raw_opt)
else:
opts.append(
{
"-b": "--number-nonblank",
"-E": "--show-ends",
"-n": "--number",
"-s": "--squeeze-blank",
"-T": "--show-tabs",
"-v": "--show-nonprinting",
}[raw_opt]
)
help_text = """Usage: cat [OPTION]... [FILE]...
Concatenate FILE(s) to standard output.
With no FILE, or when FILE is -, read standard input.
-A, --show-all equivalent to -vET
-b, --number-nonblank number nonempty output lines, overrides -n
-e equivalent to -vE
-E, --show-ends display $ at end of each line
-n, --number number all output lines
-s, --squeeze-blank suppress repeated empty output lines
-t equivalent to -vT
-T, --show-tabs display TAB characters as ^I
-u (ignored)
-v, --show-nonprinting use ^ and M- notation, except for LFD and TAB
--help display this help and exit
--version output version information and exit
Examples:
cat f - g Output f's contents, then standard input, then g's contents.
cat Copy standard input to standard output.
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Full documentation <https://www.gnu.org/software/coreutils/cat>
or available locally via: info '(coreutils) cat invocation'"""
version_text = """cat (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Torbjorn Granlund and Richard M. Stallman."""
nonprinting = {
b"\x00": b"^@",
b"\x01": b"^A",
b"\x02": b"^B",
b"\x03": b"^C",
b"\x04": b"^D",
b"\x05": b"^E",
b"\x06": b"^F",
b"\x07": b"^G",
b"\x08": b"^H",
b"\x0b": b"^K",
b"\x0c": b"^L",
b"\r": b"^M",
b"\x0e": b"^N",
b"\x0f": b"^O",
b"\x10": b"^P",
b"\x11": b"^Q",
b"\x12": b"^R",
b"\x13": b"^S",
b"\x14": b"^T",
b"\x15": b"^U",
b"\x16": b"^V",
b"\x17": b"^W",
b"\x18": b"^X",
b"\x19": b"^Y",
b"\x1a": b"^Z",
b"\x1b": b"^[",
b"\x1c": b"^\\",
b"\x1d": b"^]",
b"\x1e": b"^^",
b"\x1f": b"^_",
b"\x7f": b"^?",
b"\x80": b"M-^@",
b"\x81": b"M-^A",
b"\x82": b"M-^B",
b"\x83": b"M-^C",
b"\x84": b"M-^D",
b"\x85": b"M-^E",
b"\x86": b"M-^F",
b"\x87": b"M-^G",
b"\x88": b"M-^H",
b"\x89": b"M-^I",
b"\x8a": b"M-^J",
b"\x8b": b"M-^K",
b"\x8c": b"M-^L",
b"\x8d": b"M-^M",
b"\x8e": b"M-^N",
b"\x8f": b"M-^O",
b"\x90": b"M-^P",
b"\x91": b"M-^Q",
b"\x92": b"M-^R",
b"\x93": b"M-^S",
b"\x94": b"M-^T",
b"\x95": b"M-^U",
b"\x96": b"M-^V",
b"\x97": b"M-^W",
b"\x98": b"M-^X",
b"\x99": b"M-^Y",
b"\x9a": b"M-^Z",
b"\x9b": b"M-^[",
b"\x9c": b"M-^\\",
b"\x9d": b"M-^]",
b"\x9e": b"M-^^",
b"\x9f": b"M-^_",
b"\xa0": b"M- ",
b"\xa1": b"M-!",
b"\xa2": b'M-"',
b"\xa3": b"M-#",
b"\xa4": b"M-$",
b"\xa5": b"M-%",
b"\xa6": b"M-&",
b"\xa7": b"M-'",
b"\xa8": b"M-(",
b"\xa9": b"M-)",
b"\xaa": b"M-*",
b"\xab": b"M-+",
b"\xac": b"M-,",
b"\xad": b"M--",
b"\xae": b"M-.",
b"\xaf": b"M-/",
b"\xb0": b"M-0",
b"\xb1": b"M-1",
b"\xb2": b"M-2",
b"\xb3": b"M-3",
b"\xb4": b"M-4",
b"\xb5": b"M-5",
b"\xb6": b"M-6",
b"\xb7": b"M-7",
b"\xb8": b"M-8",
b"\xb9": b"M-9",
b"\xba": b"M-:",
b"\xbb": b"M-;",
b"\xbc": b"M-<",
b"\xbd": b"M-=",
b"\xbe": b"M->",
b"\xbf": b"M-?",
b"\xc0": b"M-@",
b"\xc1": b"M-A",
b"\xc2": b"M-B",
b"\xc3": b"M-C",
b"\xc4": b"M-D",
b"\xc5": b"M-E",
b"\xc6": b"M-F",
b"\xc7": b"M-G",
b"\xc8": b"M-H",
b"\xc9": b"M-I",
b"\xca": b"M-J",
b"\xcb": b"M-K",
b"\xcc": b"M-L",
b"\xcd": b"M-M",
b"\xce": b"M-N",
b"\xcf": b"M-O",
b"\xd0": b"M-P",
b"\xd1": b"M-Q",
b"\xd2": b"M-R",
b"\xd3": b"M-S",
b"\xd4": b"M-T",
b"\xd5": b"M-U",
b"\xd6": b"M-V",
b"\xd7": b"M-W",
b"\xd8": b"M-X",
b"\xd9": b"M-Y",
b"\xda": b"M-Z",
b"\xdb": b"M-[",
b"\xdc": b"M-\\",
b"\xdd": b"M-]",
b"\xde": b"M-^",
b"\xdf": b"M-_",
b"\xe0": b"M-`",
b"\xe1": b"M-a",
b"\xe2": b"M-b",
b"\xe3": b"M-c",
b"\xe4": b"M-d",
b"\xe5": b"M-e",
b"\xe6": b"M-f",
b"\xe7": b"M-g",
b"\xe8": b"M-h",
b"\xe9": b"M-i",
b"\xea": b"M-j",
b"\xeb": b"M-k",
b"\xec": b"M-l",
b"\xed": b"M-m",
b"\xee": b"M-n",
b"\xef": b"M-o",
b"\xf0": b"M-p",
b"\xf1": b"M-q",
b"\xf2": b"M-r",
b"\xf3": b"M-s",
b"\xf4": b"M-t",
b"\xf5": b"M-u",
b"\xf6": b"M-v",
b"\xf7": b"M-w",
b"\xf8": b"M-x",
b"\xf9": b"M-y",
b"\xfa": b"M-z",
b"\xfb": b"M-{",
b"\xfc": b"M-|",
b"\xfd": b"M-}",
b"\xfe": b"M-~",
b"\xff": b"M-^?",
}
if "--help" in opts or "--version" in opts:
for opt in opts:
if opt == "--help":
print(help_text)
sys.exit(0)
if opt == "--version":
print(version_text)
sys.exit(0)
if not file_names:
file_names = ["-"]
index = 1
exit_code = 0
for file_name in file_names:
if file_name == "-":
is_file = False
f = sys.stdin.buffer
else:
is_file = True
try:
f = open(file_name, "rb")
except FileNotFoundError:
print(
f"cat: {file_name}: No such file or directory", file=sys.stderr
)
exit_code = 1
continue
except PermissionError:
print(f"cat: {file_name}: Permission denied", file=sys.stderr)
exit_code = 1
continue
last_blank = False
while True:
line = f.readline()
if line:
blank = line == b"\n"
if blank and last_blank and "--squeeze-blank" in opts:
continue
last_blank = blank
number = ("--number-nonblank" in opts and not blank) or (
"--number" in opts and not "--number-nonblank" in opts
)
prefix = f"{index:>6}\t".encode("utf-8") if number else b""
if "--show-ends" in opts:
line = line.replace(b"\n", b"$\n")
if "--show-tabs" in opts:
line = line.replace(b"\t", b"^I")
if "--show-nonprinting" in opts:
for key, value in nonprinting.items():
line = line.replace(key, value)
sys.stdout.buffer.write(prefix + line)
sys.stdout.buffer.flush()
if number:
index += 1
else:
break
if is_file:
f.close()
sys.exit(exit_code)
python3 cat.py cat.py | headis different than that ofcat cat.py | head. you get a broken pipe error in python's output but not cat's. (maybe only sometimes? every once and a while I get a run where python doesn't give an error) \$\endgroup\$python3 cat.py --version | head cat.pyprints out the head of cat.py and a broken pipe error whilecat --version | head cat.pyjust prints out the head of cat.py \$\endgroup\$cat: option '--n' is ambiguous; possibilities: '--number-nonblank' '--number'\$\endgroup\$