0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

pythonでcatコマンドを作ってみる

Last updated at Posted at 2025-10-25

練習するときは他でよく使うツールを模倣することにしている。

cloudshell を使うと環境準備が手軽。

~/bin/cat.py
#!/bin/python
import sys
import os

opts = []
files = []

# 実行前処理
for _ in sys.argv:
    arg = _.strip()
    if arg.startswith("-"):
        opts.append(arg)
    else:
        files.append(arg)

self = files.pop(0)

# 実行
for file in files:
    if(os.path.isfile(file)):
        with open(file) as f:
            for line in f:
                print(line, end="")
# 実行権限の付与
chmod +x ~/bin/cat.py

# お試し
cat.py /etc/passwd
実行結果
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/usr/sbin/nologin
systemd-oom:x:998:998:systemd Userspace OOM Killer:/:/usr/sbin/nologin
systemd-resolve:x:193:193:systemd Resolver:/:/usr/sbin/nologin
redis6:x:997:996:Redis Database Server:/var/lib/redis6:/sbin/nologin
cloudshell-user:x:1000:995::/home/cloudshell-user:/bin/bash

使えそうな気がする。

関数の使い方を確認。

~/bin/cat.py
#!/bin/python
import sys
import os

# 関数
def subroutine(file):
    if not os.path.isfile(file):
        return

    with open(file) as f:
        for line in f:
            print(line,end="")

# 実行前処理
opts = []
files = []

for _ in sys.argv:
    arg = _.strip()
    if arg.startswith("-"):
        opts.append(arg)
    else:
        files.append(arg)

self = files.pop(0)

# 実行
for file in files:
    subroutine(file)

early return も入れてネストが浅くなったので、良くなった気がする。

標準入力の受け取り方を確認。

~/bin/cat.py
#!/bin/python
import sys
import os

# 関数
def subroutine(stream):
    for line in stream:
        print(line,end="")

def filessubroutine(files):
    for file in files:
        if not os.path.isfile(file):
            continue
        with open(file) as f:
            subroutine(f)

# 実行前処理
opts = []
files = []

for _ in sys.argv:
    arg = _.strip()
    if arg.startswith("-"):
        opts.append(arg)
    else:
        files.append(arg)

self = files.pop(0)

# 実行
if len(files) == 0:
    subroutine(sys.stdin)
else:
    filessubroutine(files)

for file in files: が関数の中に押し込まれて、構造が変わった気がするけどまあヨシ。

cat.py /etc/passwd | cat.py

引数の - は標準入力を表すので、files に入れておいて sys.stdin として扱うように。

~/bin/cat.py
# ...
def filessubroutine(files):
    for file in files:
        if file == "-":
            subroutine(sys.stdin)
            
        elif os.path.isfile(file):
            with open(file) as f:
                subroutine(f)

# ...
opts = {}

for _ in sys.argv:
    arg = _.strip()
    if not arg == "-" and arg.startswith("-"):
        opts[arg] = True
    else:
        files.append(arg)

# ...

man cat によると -T, --show-tabs はタブを ^I で表示するオプションらしい。

~/bin/cat.py
#!/bin/python
import sys
import os

# 関数
def linesubroutine(line):
    for ch in line:
        if opts.get("-T") or opts.get("--show-tabs"):
            if ch == "\t":
                ch = "^I"
        print(ch, end="")

def subroutine(stream):
    for line in stream:
        linesubroutine(line)

def filessubroutine(files):
    for file in files:
        if file == "-":
            subroutine(sys.stdin)
            
        elif os.path.isfile(file):
            with open(file) as f:
                subroutine(f)

# 実行前処理
opts = {}
files = []

for _ in sys.argv:
    arg = _.strip()
    if not arg == "-" and arg.startswith("-"):
        opts[arg] = True
    else:
        files.append(arg)

self = files.pop(0)

# 実行
if len(files) == 0:
    subroutine(sys.stdin)
else:
    filessubroutine(files)

汚くなってきたけど出来た気がする。

ちょうど /etc/csh.cshrc にタブが混ざっていたので、実行してみると cat と同じ結果が得られた。

cat.py /etc/csh.cshrc -T | grep '\^I'

同じく-E, --show-endsで行末を $ で表示するオプションらしいので追加。同じ。

~/bin/cat.py
def linesubroutine(line):
    for ch in line:
        if opts.get("-T") or opts.get("--show-tabs"):
            if ch == "\t":
                ch = "^I"

        if opts.get("-E") or opts.get("--show-ends"):
            if ch == "\n":
                ch = "$\n"

        print(ch, end="")

-n, --number は行番号を表示するとのこと。global の使い所がよくわからず。

~/bin/cat.py
# ...
linenum = 0
def linesubroutine(line):
    if opts.get("-n") or opts.get("--number"):
        global linenum
        linenum += 1
        num = str(linenum).rjust(6)
        print(num + " ", end="")

    #...

-s, --squeeze は連続する空白行を省略する。cat ではわりとよく使う。

~/bin/cat.py
# ...
def linesubroutine(line):
    if opts.get("-s") or opts.get("--squeeze-blank"):
        global lastline
        if lastline == "\n" and line == "\n":
            return
        else:
            lastline = line
# ...

満足してきたので一旦おわり。

~/bin/cat.py
#!/bin/python
import sys
import os

# 関数
def linesubroutine(line):
    if opts.get("-s") or opts.get("--squeeze-blank"):
        global lastline
        if lastline == "\n" and line == "\n":
            return
        else:
            lastline = line

    if opts.get("-n") or opts.get("--number"):
        global linenum
        linenum += 1
        num = str(linenum).rjust(6)
        print(num + " ", end="")

    for ch in line:
        if opts.get("-T") or opts.get("--show-tabs"):
            if ch == "\t":
                ch = "^I"

        if opts.get("-E") or opts.get("--show-ends"):
            if ch == "\n":
                ch = "$\n"

        print(ch, end="")

def subroutine(stream):
    for line in stream:
        linesubroutine(line)

def filessubroutine(files):
    for file in files:
        if file == "-":
            subroutine(sys.stdin)
            
        elif os.path.isfile(file):
            with open(file) as f:
                subroutine(f)

# 実行前処理
opts = {}
files = []
linenum = 0
lastline = ""

for _ in sys.argv:
    arg = _.strip()
    if not arg == "-" and arg.startswith("-"):
        opts[arg] = True
    else:
        files.append(arg)

self = files.pop(0)

# 実行
if len(files) == 0:
    subroutine(sys.stdin)
else:
    filessubroutine(files)

python にありがちな __name__ は出番が無かった。保守性とかお作法関連の機能だと思う。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?