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?

Common Lisp ベーシックマスター Level 9 (入出力とファイル操作)

0
Last updated at Posted at 2025-12-08

1. 入出力の基本

プログラムは、計算するだけでは意味がない。ユーザーからデータを受け取り、結果を表示したり、ファイルに保存したりする必要がある。この「外部とのデータのやり取り」が 入出力(I/O: Input/Output) である。

1-1. ストリームとは

Common Lisp では、入出力は ストリーム(stream) という概念を通じて行われる。ストリームとは「データの流れ」を抽象化したもので、キーボードからの入力も、ファイルへの書き込みも、同じ仕組みで扱える。

水道の蛇口をイメージするとわかりやすい。蛇口を開けると水が流れ出るように、ストリームからはデータが流れてくる。逆に、排水口に水を流すように、ストリームにデータを流し込むこともできる。

1-2. 標準ストリーム

Common Lisp には、最初から用意されている標準的なストリームがある。

ストリーム 説明 用途
*standard-input* 標準入力 キーボードからの入力
*standard-output* 標準出力 画面への出力
*error-output* エラー出力 エラーメッセージの出力

通常、私たちが printformat で出力すると、自動的に *standard-output*(画面)に表示される。入力関数 read*standard-input*(キーボード)からデータを読み取る。

2. 基本的な出力関数

2-1. print ― デバッグ向けの出力

print は、値を出力する最も基本的な関数である。出力の前に改行し、後にスペースを入れる。

(print "Hello")

実行すると、画面には以下のように表示される:

"Hello" 

print読み取り可能な形式 で出力する。つまり、文字列にはダブルクォートがつき、出力された内容をそのまま read で読み戻せる。これはデバッグやデータの保存に便利である。

2-2. prin1 ― 改行なしの読み取り可能形式

prin1print と似ているが、前後に改行やスペースを入れない。

(prin1 "Hello")
(prin1 "World")

出力:

"Hello""World"

連続して出力すると、くっついて表示される。改行が必要な場合は、別途 terpri を使う。

2-3. princ ― 人間が読みやすい形式

princ人間が読みやすい形式 で出力する。文字列のダブルクォートは表示されない。

(princ "Hello")

出力:

Hello

ユーザーへのメッセージ表示には princ が適している。一方、データを保存して後で読み戻す場合は prin1 を使う。

2-4. terpri ― 改行

terpri は改行だけを出力する。名前は「TERminate PRInt line」の略である。

(princ "Hello")
(terpri)
(princ "World")

出力:

Hello
World

2-5. 出力関数の比較

関数 改行 クォート 主な用途
print 前に改行 あり デバッグ、簡易出力
prin1 なし あり データ保存
princ なし なし ユーザー向け表示
terpri 改行のみ - 改行の挿入

3. format ― 強力な書式付き出力

3-1. format とは

format は、Common Lisp で最も強力な出力関数である。C言語の printf に似ているが、はるかに多機能である。書式文字列の中に 指定子 を埋め込み、値を整形して出力できる。

3-2. 基本構文

(format 出力先 書式文字列 引数...)

出力先 には3種類を指定できる:

出力先 動作
t 画面に出力し、NIL を返す
nil 出力せず、文字列を返す
ストリーム 指定したストリームに出力
;; 画面に出力
(format t "Hello, World!")
;; Hello, World!
;; → NIL(戻り値)

;; 文字列として取得(画面には表示されない)
(format nil "Hello, World!")
;; → "Hello, World!"

format を使って文字列を組み立てる場合は、出力先に nil を指定する。これは非常によく使うパターンである。

3-3. 基本的な書式指定子

書式文字列の中で ~ から始まる部分が 書式指定子 である。指定子の位置に、引数が順番に埋め込まれる。

指定子 意味
~a 美的(Aesthetic)表示 (format t "~a" "hello")hello
~s 標準(Standard)表示 (format t "~s" "hello")"hello"
~d 10進整数(Decimal) (format t "~d" 42)42
~f 浮動小数点数(Float) (format t "~f" 3.14)3.14
~% 改行 (format t "~%")
~~ チルダ自体を出力 (format t "~~")~

~a~s の違いは、princprin1 の違いと同じである。人間向けには ~a、データ保存には ~s を使う。

3-4. 使用例

;; 単純な値の埋め込み
(format t "名前: ~a~%" "Alice")
;; 名前: Alice

;; 複数の値を埋め込む
(format t "~a は ~d 歳です。~%" "Alice" 25)
;; Alice は 25 歳です。

;; 計算結果を埋め込む
(format t "合計: ~d~%" (+ 100 200 300))
;; 合計: 600

3-5. 数値の書式制御

数値を出力するとき、桁数や表示形式を細かく指定できる。

整数の桁数指定

;; 5桁で右寄せ(空白で埋める)
(format t "|~5d|" 42)
;; |   42|

;; 5桁でゼロ埋め
(format t "|~5,'0d|" 42)
;; |00042|

浮動小数点数の桁数

;; 全体5桁、小数点以下2桁
(format t "~5,2f" 3.14159)
;; 3.14

カンマ区切り

;; 3桁ごとにカンマ
(format t "~:d" 1000000)
;; 1,000,000

これらは、金額や統計データの表示に便利である。

3-6. 条件付き出力

~[~] で囲むと、条件に応じて異なる文字列を出力できる。

;; インデックスで選択(0から開始)
(format t "~[zero~;one~;two~]" 0)  ;; → zero
(format t "~[zero~;one~;two~]" 1)  ;; → one
(format t "~[zero~;one~;two~]" 2)  ;; → two

真偽値で分岐する場合は ~:[ を使う:

;; NIL なら最初、それ以外なら2番目
(format t "~:[失敗~;成功~]" t)    ;; → 成功
(format t "~:[失敗~;成功~]" nil)  ;; → 失敗

3-7. リストの繰り返し出力

~{~} で囲むと、リストの各要素に対して繰り返し処理ができる。

;; リストの各要素を出力
(format t "~{~a ~}" '(1 2 3 4 5))
;; 1 2 3 4 5

;; 区切り文字付き(~^ は最後の要素の後に区切りをつけない)
(format t "~{~a~^, ~}" '(a b c))
;; a, b, c

これは、リストの内容を表示するときに非常に便利である。

4. 入力関数

4-1. read ― S 式の読み取り

read は、入力から S 式 を1つ読み取る。数値を入力すれば数値が、リストを入力すればリストが得られる。

(read)
;; ユーザーが 42 と入力
;; → 42(数値として)

(read)
;; ユーザーが (1 2 3) と入力
;; → (1 2 3)(リストとして)

read は Lisp のデータ構造をそのまま読み取れるため、設定ファイルやデータファイルの読み込みに適している。

4-2. read-line ― 1行の読み取り

read-line は、入力を 1行そのまま文字列として 読み取る。スペースを含む入力も受け取れる。

(read-line)
;; ユーザーが Hello World と入力
;; → "Hello World"

ユーザーからの自由入力を受け取る場合は、read より read-line が適している。read だとスペースで区切られた部分だけを読み取ってしまうためである。

4-3. read-char ― 1文字の読み取り

read-char は、入力から 1文字だけ 読み取る。キー入力の検出などに使う。

(read-char)
;; ユーザーが a と入力
;; → #\a(文字オブジェクト)

4-4. 対話的プログラムの例

ユーザーと対話するプログラムを作ってみよう。

(defun ask-name ()
  (format t "名前を入力してください: ")
  (finish-output)  ; 出力バッファを即座に表示
  (let ((name (read-line)))
    (format t "こんにちは、~a さん!~%" name)))

finish-output は、出力バッファの内容を強制的に表示する関数である。これがないと、プロンプトが表示される前に入力待ちになることがある。

実行例:

名前を入力してください: Alice
こんにちは、Alice さん!

5. ファイル操作の基本

5-1. なぜファイル操作が必要か

プログラムが終了すると、変数に保存したデータは消えてしまう。データを永続的に保存するには、ファイルに書き込む必要がある。また、大量のデータを処理する場合も、ファイルから読み込むことが多い。

5-2. ストリームによるファイルアクセス

ファイルを操作するには、まずファイルを開いて ストリーム を作成する。このストリームを通じて読み書きを行い、終わったらファイルを閉じる。

5-3. 外部形式(external-format)― 文字エンコーディングの指定

日本語などのマルチバイト文字を含むファイルを扱う場合、外部形式 を指定する必要がある。外部形式とは、ファイル内の文字がどのようにエンコードされているかを指定するものである。

現代では UTF-8 が標準的なエンコーディングであり、ほとんどの場合 :external-format :utf-8 を指定すれば問題ない。

;; UTF-8 でファイルを開く
(open "japanese.txt" :direction :input :external-format :utf-8)

外部形式を指定しないと、システムのデフォルトエンコーディングが使われる。これは環境によって異なるため、日本語を扱う場合は必ず :external-format :utf-8 を明示的に指定する ことを推奨する。

外部形式 説明
:utf-8 UTF-8(推奨、最も汎用的)
:utf-16 UTF-16
:latin-1 Latin-1(西欧言語向け)
:default システムのデフォルト

5-4. open と close

open でファイルを開き、close で閉じる。

;; ファイルを読み取りモードで開く(UTF-8)
(defparameter *stream* (open "test.txt" 
                             :direction :input
                             :external-format :utf-8))

;; ストリームから1行読む
(read-line *stream*)

;; ファイルを閉じる
(close *stream*)

:direction オプションでモードを指定する:

オプション 意味
:input 読み取り専用
:output 書き込み専用
:io 読み書き両用

5-5. open/close の問題点

openclose を別々に呼ぶ方法には問題がある。読み取り中にエラーが発生すると、close が実行されずにファイルが開いたままになってしまう。

;; 危険なパターン
(defparameter *stream* (open "test.txt" 
                             :direction :input
                             :external-format :utf-8))
(some-function-that-might-fail)  ; ここでエラーが起きたら?
(close *stream*)  ; ← これが実行されない!

この問題を解決するのが with-open-file である。

6. with-open-file ― 安全なファイル操作

6-1. with-open-file の構文

(with-open-file (ストリーム変数 ファイル名 オプション...)
  本体...)

with-open-file は、本体の処理が終わると 自動的にファイルを閉じる。エラーが発生した場合でも確実に閉じられる。これは「リソースの自動解放」と呼ばれるパターンで、安全なプログラミングに欠かせない。

6-2. ファイルの読み取り

ファイルを1行ずつ読み取る基本パターン:

(with-open-file (in "data.txt" 
                    :direction :input
                    :external-format :utf-8)
  (loop for line = (read-line in nil)
        while line
        do (format t "~a~%" line)))

read-line の引数:

  • 第1引数:読み取るストリーム
  • 第2引数:EOF(ファイル末尾)でエラーにするか(nil = エラーにしない、NIL を返す)

loop で繰り返し読み取り、lineNIL(ファイル末尾)になったら終了する。

6-3. ファイルへの書き込み

ファイルに書き込む基本パターン:

(with-open-file (out "output.txt" 
                     :direction :output
                     :if-exists :supersede
                     :external-format :utf-8)
  (format out "1行目~%")
  (format out "2行目~%"))

:if-exists オプションは、ファイルが既に存在する場合の動作を指定する:

オプション 意味
:supersede 既存ファイルを削除して新規作成(上書き)
:append 既存ファイルの末尾に追記
:error エラーを発生させる(デフォルト)
:overwrite 既存ファイルを先頭から上書き

:if-does-not-exist オプションは、ファイルが存在しない場合の動作を指定する:

オプション 意味
:create 新規作成(デフォルト)
:error エラーを発生させる

6-4. ファイルへの追記

ログファイルのように、既存の内容を残して追記したい場合:

(with-open-file (out "log.txt"
                     :direction :output
                     :if-exists :append
                     :if-does-not-exist :create
                     :external-format :utf-8)
  (format out "[INFO] 処理が完了しました~%"))

この設定なら、ファイルが存在すれば追記、存在しなければ新規作成される。

7. S 式の読み書き

7-1. Lisp データの永続化

Common Lisp の大きな強みは、Lisp のデータ構造(リスト、シンボル、数値など)を そのままファイルに保存し、そのまま読み戻せる ことである。

他の言語では、データを保存するために JSON や XML などの形式に変換する必要がある。しかし Lisp では、S 式がそのまま保存形式になる。

7-2. データを保存する

printprin1 でデータをストリームに出力すると、S 式形式で保存される。

;; 保存したいデータ
(defparameter *data* 
  '((name . "Alice")
    (age . 25)
    (scores . (80 90 85))))

;; ファイルに保存
(with-open-file (out "data.lisp"
                     :direction :output
                     :if-exists :supersede
                     :external-format :utf-8)
  (print *data* out))

ファイルの内容:

((NAME . "Alice") (AGE . 25) (SCORES 80 90 85))

7-3. データを読み込む

read でストリームから読み取ると、S 式がそのまま Lisp のデータとして復元される。

;; ファイルから読み込み
(defparameter *loaded-data*
  (with-open-file (in "data.lisp" 
                      :direction :input
                      :external-format :utf-8)
    (read in)))

;; 読み込んだデータを使う
(cdr (assoc 'name *loaded-data*))
;; → "Alice"

これは非常に便利で、設定ファイル、ユーザーデータ、キャッシュなど、様々な用途に使える。

7-4. 複数のデータを読み書きする

1つのファイルに複数の S 式を保存することもできる。

;; 複数のデータを保存
(with-open-file (out "multi.lisp"
                     :direction :output
                     :if-exists :supersede
                     :external-format :utf-8)
  (print '(item1 a b c) out)
  (print '(item2 x y z) out)
  (print '(item3 1 2 3) out))

;; 複数のデータを読み込み
(with-open-file (in "multi.lisp" 
                    :direction :input
                    :external-format :utf-8)
  (loop for item = (read in nil :eof)
        until (eq item :eof)
        collect item))
;; → ((ITEM1 A B C) (ITEM2 X Y Z) (ITEM3 1 2 3))

read の第3引数は、EOF に達したときに返す値である。ここでは :eof というシンボルを指定し、それが返されたらループを終了している。

8. パス操作

8-1. パス名とは

ファイルの場所を指定する文字列を パス名 という。Common Lisp には、パス名を扱うための専用の機能がある。

パス名は複数の要素から構成される:

要素 意味
directory ディレクトリ /home/user/
name ファイル名(拡張子を除く) data
type 拡張子 txt

8-2. make-pathname ― パスの構築

プログラムでパスを組み立てるには make-pathname を使う。

(make-pathname :directory '(:absolute "home" "user")
               :name "data"
               :type "txt")
;; → #P"/home/user/data.txt"

:absolute は絶対パス、:relative は相対パスを表す。

#P について

#Pパス名オブジェクト を表すリーダーマクロである。文字列とは異なり、パス名オブジェクトはディレクトリ・ファイル名・拡張子を構造として持つ。

;; 文字列(単なる文字の並び)
"/home/user/data.txt"

;; パス名オブジェクト(構造を持つ)
#P"/home/user/data.txt"

probe-filedirectory などの関数はパス名オブジェクトを返す。通常のファイル操作では文字列を渡しても自動変換されるため、#P を明示的に書く必要はない。

;; どちらも同じ動作
(open "/home/user/data.txt" :direction :input)
(open #P"/home/user/data.txt" :direction :input)

他の主なリーダーマクロ:

記法 意味
#P パス名 #P"/home/user"
#\ 文字 #\a #\Space
#' 関数参照 #'length
#( ベクタ #(1 2 3)

8-3. パス名の分解

パス名から各要素を取り出すには、専用の関数を使う。

(defparameter *path* #P"/home/user/data.txt")

(pathname-directory *path*)
;; → (:ABSOLUTE "home" "user")

(pathname-name *path*)
;; → "data"

(pathname-type *path*)
;; → "txt"

8-4. merge-pathnames ― パスの結合

相対パスと基準パスを結合するには merge-pathnames を使う。

(merge-pathnames "subdir/file.txt" "/home/user/")
;; → #P"/home/user/subdir/file.txt"

8-5. probe-file ― ファイルの存在確認

ファイルが存在するかどうかを確認するには probe-file を使う。

;; ファイルが存在する場合:完全なパス名を返す
(probe-file "data.txt")
;; → #P"/full/path/to/data.txt"

;; ファイルが存在しない場合:NIL を返す
(probe-file "nonexistent.txt")
;; → NIL

ファイルを開く前に存在確認をしておくと、より親切なエラーメッセージを表示できる。

8-6. directory ― ディレクトリの一覧

ディレクトリ内のファイルを列挙するには directory を使う。ワイルドカード * で条件を指定できる。

;; .txt ファイルの一覧
(directory "/home/user/*.txt")
;; → (#P"/home/user/a.txt" #P"/home/user/b.txt" ...)

;; すべてのファイル
(directory "/home/user/*.*")

9. 実践例

9-1. 設定ファイルの読み書き

アプリケーションの設定を S 式で保存・読み込みするモジュール:

;; 設定を保存
(defun save-config (config filename)
  (with-open-file (out filename
                       :direction :output         ; 書き込みモード
                       :if-exists :supersede      ; 既存ファイルは上書き
                       :external-format :utf-8)   ; UTF-8 で保存
    (print config out))                           ; S式としてファイルに出力
  (format t "設定を保存しました: ~a~%" filename)) ; 完了メッセージを表示

;; 設定を読み込み(ファイルがなければデフォルト値を返す)
(defun load-config (filename &optional default)
  (if (probe-file filename)                       ; ファイルが存在するか確認
      (with-open-file (in filename 
                          :direction :input       ; 読み取りモードで開く
                          :external-format :utf-8); UTF-8 で読み込み
        (read in))                                ; S式として読み込む
      default))                                   ; なければデフォルト値を返す

使用例

;; 設定を保存
(save-config '((host . "localhost")
               (port . 8080)
               (debug . t))
             "config.lisp")
;; 設定を保存しました: config.lisp

;; 設定を読み込み
(defparameter *config* (load-config "config.lisp"))
(cdr (assoc 'port *config*))
;; → 8080
config.lisp
((HOST . "localhost") (PORT . 8080) (DEBUG . T)) 

9-2. テキストファイルの行単位処理

ファイルの各行に対して処理を行う汎用関数:

(defun process-lines (filename func)
  "ファイルの各行に対して関数を適用する"
  (with-open-file (in filename 
                      :direction :input
                      :external-format :utf-8)
    (loop for line = (read-line in nil)
          while line
          do (funcall func line))))

使用例:

;; 各行を表示
(process-lines "data.txt" #'print)

;; 行数を数える
(let ((count 0))
  (process-lines "data.txt" 
                 (lambda (line) 
                   (declare (ignore line))
                   (incf count)))
  count)

9-3. 簡易 CSV パーサー

CSV(カンマ区切り値)ファイルを読み取る簡易的な実装:

;; 文字列を区切り文字で分割
(defun split-string (str delimiter)
  (let ((result nil)                              ; 結果を格納するリスト
        (start 0))                                ; 現在の部分文字列の開始位置
    (loop for i from 0 below (length str)         ; 文字列を1文字ずつ走査
          when (char= (char str i) delimiter)     ; 区切り文字を見つけたら
          do (push (subseq str start i) result)   ; 開始位置から現在位置までを追加
             (setf start (1+ i))                  ; 開始位置を次の文字に更新
          finally (push (subseq str start) result)) ; 最後の部分を追加
    (nreverse result)))                           ; 逆順になっているので反転

;; CSV ファイルを読み取り、リストのリストとして返す
(defun read-csv (filename)
  (with-open-file (in filename 
                      :direction :input           ; 読み取りモードで開く
                      :external-format :utf-8)    ; UTF-8 で読み込み
    (loop for line = (read-line in nil)           ; 1行ずつ読む(EOFでNIL)
          while line                              ; 行がある間繰り返す
          collect (split-string line #\,))))      ; カンマで分割して収集

data.csv の内容:

Alice,25,東京
Bob,30,大阪
Carol,28,京都

使用例:

(read-csv "data.csv")
;; → (("Alice" "25" "東京") 
;;    ("Bob" "30" "大阪") 
;;    ("Carol" "28" "京都"))

9-4. ログファイルへの出力

タイムスタンプ付きでログを記録する関数:

(defun log-message (message &optional (filename "app.log"))
  "タイムスタンプ付きでメッセージをログファイルに追記する"
  (with-open-file (out filename
                       :direction :output
                       :if-exists :append
                       :if-does-not-exist :create
                       :external-format :utf-8)
    (multiple-value-bind (sec min hour day month year)
        (get-decoded-time)
      (format out "[~4d-~2,'0d-~2,'0d ~2,'0d:~2,'0d:~2,'0d] ~a~%"
              year month day hour min sec message))))

使用例:

(log-message "アプリケーション開始")
(log-message "ユーザーがログイン")
(log-message "処理完了")

app.log の内容:

[2024-01-15 10:30:45] アプリケーション開始
[2024-01-15 10:30:46] ユーザーがログイン
[2024-01-15 10:31:02] 処理完了

10. 練習課題

課題1:format の練習

以下の出力を format で作成せよ。

Name: Alice
Age:    25
Score: 85.50

解答

(format t "Name: ~a~%Age: ~5d~%Score: ~5,2f~%"
        "Alice" 25 85.5)

課題2:ファイルへの書き込み

リストの内容を1行ずつファイルに書き込む関数 write-lines を作れ。

(write-lines '("Line 1" "Line 2" "Line 3") "output.txt")

解答

;; リストの各要素を1行ずつファイルに書き込む
(defun write-lines (lines filename)
  (with-open-file (out filename
                       :direction :output        ; 書き込みモード
                       :if-exists :supersede     ; 既存ファイルは上書き
                       :external-format :utf-8)  ; UTF-8 で保存
    (dolist (line lines)                         ; リストの各要素を順に処理
      (format out "~a~%" line))))                ; 1行出力して改行

実行例

(write-lines '("おはよう" "こんにちは" "こんばんは") "greeting.txt")
;; → NIL

課題3:ファイルからの読み取り

ファイルの全行をリストとして返す関数 read-lines を作れ。

解答

;; ファイルの全行をリストとして返す
(defun read-lines (filename)
  (with-open-file (in filename 
                      :direction :input           ; 読み取りモードで開く
                      :external-format :utf-8)    ; UTF-8 で読み込み
    (loop for line = (read-line in nil)           ; 1行ずつ読む(EOFでNIL)
          while line                              ; 行がある間繰り返す
          collect line)))                         ; 各行をリストに収集

実行例

(read-lines "greeting.txt")
;; → ("おはよう" "こんにちは" "こんばんは")

課題4:単語数カウント

ファイル内の単語数を数える関数 count-words を作れ。(単語はスペースで区切られているとする)

解答

;; ファイル内の単語数を数える(スペース区切り)
(defun count-words (filename)
  (let ((count 0))                                  ; 単語数カウンタ
    (with-open-file (in filename 
                        :direction :input           ; 読み取りモードで開く
                        :external-format :utf-8)    ; UTF-8 で読み込み
      (loop for line = (read-line in nil)           ; 1行ずつ読む
            while line                              ; 行がある間繰り返す
            do (incf count                          ; カウンタに加算
                     (length (split-string line #\Space))))) ; 行内の単語数
    count))                                         ; 最終結果を返す

;; split-string は 9-3 で定義済み

課題5:連想リストの保存と読み込み

連想リストをファイルに保存し、読み込む関数ペアを作れ。

解答

;; 連想リストをファイルに保存
(defun save-alist (alist filename)
  (with-open-file (out filename
                       :direction :output         ; 書き込みモード
                       :if-exists :supersede      ; 既存ファイルは上書き
                       :external-format :utf-8)   ; UTF-8 で保存
    (print alist out)))                           ; S式として出力

;; 連想リストをファイルから読み込み
(defun load-alist (filename)
  (with-open-file (in filename 
                      :direction :input           ; 読み取りモードで開く
                      :external-format :utf-8)    ; UTF-8 で読み込み
    (read in)))                                   ; S式として読み込む

;; テスト
(save-alist '((a . 1) (b . 2) (c . 3)) "alist.lisp")
(load-alist "alist.lisp")
;; → ((A . 1) (B . 2) (C . 3))

課題6:ファイルのコピー

ファイルを別の場所にコピーする関数 copy-file を作れ。

解答

;; ファイルを別の場所にコピー
(defun copy-file (source dest)
  (with-open-file (in source 
                      :direction :input           ; コピー元を読み取りモードで開く
                      :external-format :utf-8)    ; UTF-8 で読み込み
    (with-open-file (out dest
                         :direction :output       ; コピー先を書き込みモードで開く
                         :if-exists :supersede    ; 既存ファイルは上書き
                         :external-format :utf-8) ; UTF-8 で保存
      (loop for line = (read-line in nil)         ; 1行ずつ読む
            while line                            ; 行がある間繰り返す
            do (format out "~a~%" line)))))       ; コピー先に書き込む

;; テスト
(copy-file "source.txt" "dest.txt")

11. まとめ

出力関数の使い分け

目的 関数
簡単なデバッグ print
書式付き出力 format
データ保存(読み戻し可能) prin1
人間向け表示 princ

format の主要な書式指定子

指定子 意味 用途
~a 美的表示 人間向け
~s S式表示 データ保存
~d 整数 数値表示
~f 浮動小数点 数値表示
~% 改行 改行挿入
~{~} リストの繰り返し リスト表示

ファイル操作のパターン

読み取りの基本形:

(with-open-file (in "file.txt" 
                    :direction :input
                    :external-format :utf-8)
  (loop for line = (read-line in nil)
        while line
        do (処理)))

書き込みの基本形:

(with-open-file (out "file.txt"
                     :direction :output
                     :if-exists :supersede
                     :external-format :utf-8)
  (format out "~a~%" data))

日本語ファイルを扱う際の注意点

日本語を含むファイルを読み書きする際は、必ず :external-format :utf-8 を指定する。これを忘れると、文字化けやエラーの原因になる。

S 式の永続化

Lisp のデータは print で保存し、read で復元できる。これは他の言語にない Lisp の強みである。

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?