🐥

【ポートフォリオ 02】Pythonで作るコマンドラインファイル操作ツール file_manipulator.pyCLI初心者ファイル

に公開

はじめに

前回に引き続き、Pythonを使いながらコマンドラインから指定されたファイルに対して、ファイル操作を行なうメソッドを実装しました。
(前回記事の参考: https://qiita.com/mabo23/items/b0c5fcf4e2bc35348b7f

この記事では、学習のアウトプットとして実装した「File Manipulator Program」を紹介します。

課題概要

本課題は、プログラミング学習サイト「RecursionCS」にて出題された演習問題をもとにしています。

【課題】

この課題では、Pythonでコマンドラインツールを実装する力を養うことを目的とします。
コマンドラインから指定されたファイルに対して、様々な操作を行なうスクリプト file_manipulator.py を作成してください。

【要件】

以下の4つのサブコマンドをサポートするようにしてください。

1. reverse inputpath outputpath

  • 指定された inputpath のファイル内容を逆順にして、outputpath に保存する
  • 行単位ではなく、全体の文字列を逆順にすること(例:「ABC」→「CBA」)
python file_manipulator.py reverse sample.txt reversed.txt

2. copy inputpath outputpath

  • inputpath の内容をそのまま outputpath にコピーする
python file_manipulator.py copy original.txt copy.txt

3. duplicate-contents inputpath n

  • inputpath のファイル内容を読み込み、それを n 回繰り返して上書き保存する
  • n は正の整数であること
python file_manipulator.py duplicate-contents example.txt 3

→ example.txt の内容が 3 回連続で繰り返されて保存される

4. replace-string inputpath needle newstring

  • inputpath のファイル中から、文字列 needle をすべて検索し、newstring に置き換える
  • 結果は元の inputpath に上書き保存する
python file_manipulator.py replace-string diary.txt happy joyful

→ diary.txt 内の "happy" がすべて "joyful" に置き換わる

【バリデーション要件】

  • 引数の数や型が正しいかを必ず検証すること
    (例)ファイルが存在するか、数値が正の整数か、など
  • 無効な引数の場合は、適切なエラーメッセージを表示して処理を終了すること

開発環境

  • 使用PC:MacBook Pro2(2023年モデル)
  • OS:macOS Sequoia 15.5
  • Pythonバージョン:3.13.5(Homebrewでインストール)
  • エディタ:Visual Studio Code(VSCode)
  • 実行:ターミナル上で python3 file_manipulator.py コマンドを使用

目的

  • コマンドラインを通じて、ファイル操作を実現すること
  • シェルの基本的な理解・ファイル操作の流れのポイントを押さえること

実装方針

  • コマンドラインの最初の引数(コマンド名)ごとに処理用の関数を作成
  • 引数のバリデーション(文字列チェック、ファイル存在確認、数値チェック)
  • コマンドごとに分かりやすく役割を分離した関数を4つ用意
  • 共通の処理は関数化して重複を減らす

各メソッド

1. reverse inputpath outputpath

  • 引数のバリデーション(ファイルの存在確認)
  • ファイル内容を読み取り、変数に格納
  • 読み取ったファイルオブジェクトから1行ずつ文字列を取り出し、forループによって文字列の後ろから新しいファイルに書き込みしていく
  • 変数を使用し、ファイルへの書き込み

2. copy inputpath outputpath

  • 引数のバリデーション(ファイルの存在確認)
  • ファイル内容を読み取り、変数に格納
  • 変数を使用し、ファイルへの書き込み

3. duplicate-contents inputpath n

  • 引数のバリデーション(ファイルの存在確認・数値チェック)
  • ファイル内容を読み取り、変数に格納
  • 受け取ったを数値を for ループの回数に指定する
  • 指定された回数分、変数を使用し、ファイルへ書き込み

4. replace-string inputpath needle newstring

  • 引数のバリデーション(ファイルの存在確認・文字列チェック)
  • ファイル内容を読み取り、変数に格納
  • replace()を使ってneedlenewstringに書き換えて、変数に格納
  • 変数を使用し、ファイルへの書き込み

追加タスク

実装し始めてから以下のタスクを追加し、関数の共通化を行ないました。

  • ファイル読み取りのための関数
  • ファイル書き込みのための関数
  • バリデーションにより検証後の変数を返す設計に変更

実装中に直面した課題

  • 上記の実装方針で「読み取ったファイルオブジェクトから1行ずつ文字列を取り出し、forループによって文字列の後ろから新しいファイルに書き込みしていく」としたが、これだと上から処理されるため、内容が逆順にはならなかったこと
    → スライス構文を使うことで逆から処理することができた

エラー

エラー①: error: '3' is not an integer

実行したコマンド
python3 /Users/project/backend-portfolio/02_file_manipulator_program/file_manipulator.py duplicate-contents example.txt 3
実行結果(独自出力)
error: '3' is not an integer. Please provide a valid integer.

【原因】sys.argv の中身はすべて文字列(str 型)である
【解決策】int()によって、整数に変換すること

改善したこと

1. 責任の分離

改善前
n = int(sys.argv[3])
改善後
n = validate_int(sys.argv[3])

2. バリデーションの追加
当初、ファイルの読み込みと書き込みの関数にそれぞれバリデーションがありませんでした

学び

  • いかに全体の設計の段階で解像度高く、必要タスクを洗い出せるかがコーディング時に手が止まるかどうかを決めるということを思い知りました

  • コマンドラインの各コマンドの意味について以下のことを知りました

コマンド例
python3 file_manipulator.py reverse inputpath outputpath

python3: OSが「Pythonインタプリタ」を起動するためのコマンド
file_manipulator.py: 実行するスクリプト名
reverse inputpath outputpath: スクリプトに渡す引数

  • sys.argvでは、Pythonインタプリタを起動するためのコマンドは対象外であるということ

  • input()sys.stdin.readline()を使用していないにもかかわらず、Pythonプログラムに入力データが渡っているのは、実行時にsys.argvによってコマンドライン引数が渡されているからである、ということ

次回に向けて

  • 設計の段階でどんな関数をどんな目的で作るかを明確化する
  • あとはコーディングで翻訳するだけと言えるレベルまで言語化する
  • 共通化できる部分をできるだけ実装前に洗い出す

まとめ

今回の課題では、Pythonを使ってファイルの読み書き、指定ファイルの反転、コピー、複製、文字列の置換を行なうことができるメソッドを実装しました。
実際に手を動かし始めてから気づくことも多くありましたが、手を動かす前にどこまで細かくタスクの内容を言語化できるかが実装スピードに影響を与えると実感しました。
次回以降、上記の「次回に向けて」に列挙した点などに気をつけながら、プログラムに取り組んでいきます。
最後までお読みいただき、ありがとうございました。

参考URL

https://note.nkmk.me/python-os-exists-isfile-isdir/#google_vignette
https://docs.python.org/ja/3.13/library/os.path.html#module-os.path
https://www.sejuku.net/blog/24331
https://docs.python.org/ja/3.13/library/sys.html#sys.exit
https://note.nkmk.me/python-reverse-reversed/#_3
https://docs.python.org/ja/3/library/io.html#io.TextIOWrapper
https://stackoverflow.com/questions/69855463/search-and-replace-lines-in-a-file-in-python
https://gammasoft.jp/blog/text-file-edit-by-python/
https://note.nkmk.me/python-type-isinstance/
https://qiita.com/fu-a-sak/items/6879cb065ad4c5f0b901
https://note.nkmk.me/python-if-name-main/
https://docs.python.org/ja/3/library/main.html
https://python.keicode.com/lang/path-get-abspath.php

Discussion