Wejście-wyjście

Operacje czytania i pisania w Lispie działają na strumieniach. Strumieniami zajmiemy się póżniej, na razie zajmijmy się funkcjami operującymi na nich.

Zestaw klasyczny

Funkcja read wczytuje z wejścia poprawny pojedynczy obiekt Lispu (na przykład listę lub symbol)

(read [strumień [błąd-eof?] [eof] [rekurencyjne?])

Strumień (wejściowy) domyślnie jest równy nil, co oznacza *standard-input*. Jeśli jest równy t, to czytanie odbywa się z *terminal-io*. No i oczywiście może być strumieniem.

Argument błąd-eof? określa, co ma się stać po napotkaniu końca strumienia (na przykład pliku). Domyślna wartośc t oznacza sygnalizację błędu. Natomiast przy wartości nil zwykle nie ma błędu i zwraca się wartość argumentu eof (domyślnie nil).

Jednak jeśli napotkamy koniec pliku w środku obiektu, to zawsze dostaniemy błąd.

W przykładach dla prostoty użyjemy strumieni otwartych na napisach

* (defparameter input (make-string-input-stream "foo bar #*01101 #o12"))
INPUT

* (read input)
FOO

* (read input)
BAR

* (read input)
#*01101

* (read input)
10

* (read input nil 'gotowe)
GOTOWE

* (read input)
debugger invoked on a END-OF-FILE in thread
#:
  end of file on #

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-IMPL::STRING-INCH # T 0)
0] 0

Funkcja listen sprawdza, czy są jakieś znaki na wejściu

(listen [strumień])
Zwraca t jeśli tak jest, nil w przeciwnym razie. Używana głównie dla strumieni interakcyjnych.

Strumień ma dwie wartości specjalne. Wartość nil (domyślna) oznacza *standard-input*, zaś t oznacza *terminal-io*.

* (listen)
NIL

* (with-open-file (in "foo")
    (listen in))
T

Bazową funkcją do wypisywania jest write, zaopatrzona w parametry kluczowe na wszystkie możliwe okazje. Na codzień zamiast niej używa się jednak innych funkcji.

Funkcje

(princ obiekt [strumień])
(prin1 obiekt [strumień])
wypisują obiekt na strumień wyjściowy (domyślnie *standard-output*). Pierwsza z nich czyni to w sposób czytelniejszy dla człowieka (np. napisy bez cudzysłowów).

Druga natomiast pisze w taki sposób, żeby można było obiekt z powrotem wczytać. Nie zawsze jest to możliwe, wtedy wypisywana informacja ma postać

#<...>

Funkcja terpri przechodzi do nowej linii na wyjściu. Często można jej uniknąć używając funkcji print. Przechodzi ona do nowej linii, po czym wypisuje obiekt i spację.

Funkcja

(finish-output [strumień])
wymusza wypisanie na strumień wyjściowy wszystkich znaków w buforze wyjścia. Działa synchronicznie, zwraca nil dopiero wtedy, gdy wszystkie znaki zostaną wypisane.

Argument może być strumieniem, nil lub t. Domyślną wartością jest nil, co oznacza *standard-output*. Argument t oznacza *terminal-io*.

Ta i następna funkcja są ważne, ponieważ współczesne implementacje Lispu (na przykład dla Unixa) pracują na buforowanym wyjściu. Znaki są wypisywane dopiero po wysłaniu znaku nowej linii.

Funkcja

(force-output [ [strumień])
także wymusza wypisanie na strumień wyjściowy wszystkich znaków w buforze wyjścia, ale nie czeka na zakończenie.

Czytanie

Funkcja peek-char ,,podgląda'' następny znak do przeczytania z wejścia

(peek-char [typ [strumień [błąd-eof?] [eof] [rekurencyjne?])
Normalnie funkcja ta zwraca następny znak z wejścia bez pobierania go, to znaczy bez przesuwania wskaźnika bieżącego znaku. Tak dzieje się, gdy argument type jest równy nil (wartość domyślna).

Jeśli jednak typ jest równy t, najpierw wczytuje się i pomija wiodące whitespace znaki (ale komentarze nie są pomijane), a dopiero potem ogląda pierwszy znak. W tym przypadku wskaźnik bieżącego położenia będzie przesunięty za białe znaki.

Jeśli typ jest znakiem, pomija się wszystkie znaki różne od niego (w sensie char=, potem zwraca ten znak bez czytania.

Strumień wejściowy domyślnie jest równy nil, co oznacza *standard-input*. Jeśli jest równy t, to czytanie odbywa się z *terminal-io*. No i oczywiście może być strumieniem.

Znaczenie pozostałych argumentów jest opisane w funkcji read.

* (progn (peek-char #\p) (read))
ala ma psa
PSA

* (with-open-file (out "foo" :direction :output
                   :if-exists :supersede
                   :if-does-not-exist :create)
    (princ "trochę znaków na foo" out))
"trochę znaków na foo"

* (with-open-file (in "foo")
    (list (print (read in))
          (print (peek-char nil in))
          (print (read in))
          (print (peek-char nil in))
          (print (read in))
          (print (peek-char t in))
          (print (read in))
          (print (peek-char #\f in nil :eof))
          (print (read in nil :eof))))

TROCHĘ 
#\z 
ZNAKÓW 
#\n 
NA 
#\f 
FOO 
:EOF 

(TROCHĘ #\z ZNAKÓW #\n NA #\f FOO :EOF :EOF):EOF 

Funkcja read-byte wczytuje następny bajt ze strumienia i zwraca jego wartość jako liczbę całkowitą.

(read-byte [strumień [błąd-eof?] [eof] [rekurencyjne?])
Rozmiar bajtu zależy od typu strumienia. Parametry jak dla funkcji read.
* (let* ((out (open "junk" :direction :output :if-exists :supersede
                    :element-type '(unsigned-byte 8)))
         (in (open "junk" :direction :input
                   :element-type '(unsigned-byte 8))))
    (write-byte #b10010101 out) (write-byte #b11101001 out)
    (close out)
    (format t "~8B " (read-byte in))
    (format t "~8B ~%" (read-byte in))
    (close in))
10010101 11101001 
T

Funkcja read-char-no-hang czyta znak ze strumienia, o ile jest dostępny (czytanie nieblokujące)

(read-char-no-hang [typ [strumień [błąd-eof?] [eof] [rekurencyjne?])

Zwraca wczytany znak lub nil, jeśli nie można go w tej chwili wczytać. Poza tym działa jak read-char

* (read-char-no-hang *terminal-io*)
NIL
Uwaga: dla powyższego przykładu bywają implementacje zwracające znak nowej linii.

Funkcja read-delimited-list czyta obiekty z wejścia aż do napotkania podanego znaku

(read-delimited-list znak [strumień] [recursive-p])
Zwraca listę wczytanych obiektów.

Uwaga: jeśli znak wystąpi wewnątrz reprezentacji obiektu, to nie jest traktowany specjalnie.

Nie ma parametrów dla końca pliku: w tej funkcji napotkanie końca pliku jest błędem.

* (defparameter in
    (make-string-input-stream "now is the time for all good men"))
IN

* (read-delimited-list #\m in)
(NOW IS THE TIME FOR ALL GOOD)

* (read in)
EN

Pisanie

Funkcja write-char wypisuje znak na strumień

(write-char znak [strumień])

Argument stream powinien określać strumień. Jeśli jest to symbol nil, to wypisujemy na *standard-output*, jeśli t, to na *terminal-io*. Poza tym może to być po prostu strumień. Przykład

* (defparameter strumyk (make-string-output-stream))
STRUMYK

* (progn (write-char #\L strumyk)
         (write-char #\i strumyk)
         (write-char #\s strumyk)
         (write-char #\p strumyk))
#\p

* (get-output-stream-string strumyk)
"Lisp"

Napis można wypisać jednym wywołaniem funkcji write-string. Potrafi ona też wypisywać podnapisy.

(write-string napis [strumień] [:start p] [:end k])

Wypisuje cały napis, strumień jak poprzednio.

Jeśli podano argumenty :start lub :end, wypisany będzie jedynie wskazany podnapis.

Zawsze jednak zwraca cały napis. Przykład

* (progn (write-string "Wszystkie znaki ...")
         (write-string "w jednym wierszu"))
Wszystkie znaki ...w jednym wierszu
"w jednym wierszu"

Funkcja write-line działa podobnie do poprzedniej, ale wypisuje na koniec znak nowego wiersza.

(write-line napis [strumień] [:start p] [:end k])
Przykład
* (progn (write-line "Pora na ...")
         (write-string "nowy wiersz"))
Pora na ...
nowy wiersz
"nowy wiersz"

Funkcja write-byte zamiast znakami operuje ,,bajtami'', jednak tak naprawdę te bajty mogą być różnych rozmiarów. Jest to jednak pisanie binarne.

(write-byte integer strumień)

Strumień musi być strumieniem binarnym, czyli takim, że typem jego elementów jest jakiś skończony podbiór liczb całkowitych.

Typ pierwszego argumentu musi pasować do typu strumienia. Przykład

* (setq *print-base* 2)
10

* (with-open-file (out "junk"
                       :direction :output
                       :if-exists :supersede
                       :element-type '(unsigned-byte 8))
    (write-byte #b10010101 out)
    (finish-output out))
NIL

* (with-open-file (in "junk"
                        :direction :input
                        :element-type '(unsigned-byte 8))
    (read-byte in))
10010101

Funkcja write-to-string wypisuje obiekt do (nowo utworzonego) napisu i zwraca go

(write-to-string obiekt
                 &key escape radix base circle pretty level length
                      case gensym array lines pprint-dispatch readably
                      right-margin miser-width)
Argumenty kluczowe (o których na razie wolicie nie wiedzieć) są takie same jak dla bazowej funkcji write.
* (write-to-string '(this is a test list))
"(this is a test list)"

Strumienie

Operacje czytania i pisania w Lispie działają na strumieniach. Strumień jest najczęściej obiektem pośredniczącym przy dostępie do pliku. Ale można go również podłączać gdzie indziej.

Proste funkcje wejścia-wyjścia jako pierwszy opcjonalny argument biorą strumień. Gdy go nie ma, to standardowe wejście i wyjście (mniej więcej).

Funkcja open otwiera strumień dla podanego nazwą pliku

(open plik [:direction in-out] [:element-type typ]
                  [:if-exists akcja]
      [:if-does-not-exist akcja-2] [:external-format format]
Pierwszy argument może być pathname, napisem lub strumieniem. Jeśli jest strumieniem, tworzy się nowy strumień do tego samego pliku.

Strumienie należy zamykać (funkcją close). Czasem (błędy, wyjatki) może to być trudne, dlatego zamiast open lepiej wtedy użyć makra with-open-file, gwarantującego zamknięcie pliku.

Wartością argumentu :direction jest jeden z symboli :input (domyślnie), :output, :io lub :probe. Pierwsze trzy są oczywiste, :probe służy do sprawdzania, czy plik istnieje i wtedy nie tworzymy strumienia (a raczej tworzymy i natychmiast zamykamy).

Argument :element-type podaje typ elementów plik: jakiś typ znakowy lub całkowity. Może to też być symbol :default, wtedy może to być wymuszone przez rodzaj pliku, a jeśli nie to character.

Argument :if-exists określa, co ma się stać, jeśli plik istnieje (dotyczy pisania). Na ogół bedzie to :append, :overwrite lub :supersede, co oznacza nadpisywanie pliku. Jeśli nie życzymy sobie tego, używamy :new-version lub :error. Domyślna wartość to :error.

Argument :if-does-not-exist może być równy :error (domyślnie gdy użyto kierunku :input lub gdy :if-exists ma wartości :append lub :overwrite) lub :create (domyślne dla kierunków :output i :io, gdy :if-exists nie jest :append ani :overwrite). Może też być nil (domyślne dla :probe).

Argument :external-format ma tylko standardową wartość :default. Używany bywa przez implementacje do czytania plików o specyficznym kodowaniu znaków.

* (defparameter s1 (open "newfile" :direction :output
                         :if-exists :supersede
                         :if-does-not-exist :create))
S1

* s1
#

* (print "jeden" s1)
"jeden"

* (close s1)
T

Funkcja (close strumień [abort?]) zamyka strumień (nie tylko plikowy, ale także napisowy lub sieciowy. Zwraca t jeśli się udało (bo terminala głównego się zapewne nie uda). Strumień staje się ,,closed stream''. Funkcją open-stream-p można sprawdzić ,czy strumień jest otwarty.

Jeśli dodatkowy parametr jest t, to próbuje się odwrócić efekty uprzedniego otwarcia strumienia, na przykład usuwa utworzony wtedy plik.

Wiele kłopotu w prostych językach sprawiają bufory: zawsze są za małe. A przepełnienie bufora to wykroczenie przeciwko bezpieczeństwu. W Lispie wystarczy użyć rozszerzalnych wektorów.

Napisy mogą być jednak podłączane do strumieni. Niskopoziomowe funkcje to make-string-input-stream i make-string-output-stream. Funkcja

(make-string-input-stream napis [start] [end])
Tworzy strumień, dla których czytanie będzie się odbywało z podanego napisu.
* (defparameter s1 (make-string-input-stream "abc def"))
S1
* (read s1)
ABC
* (close s1)
T
* s1
#
* (read s1)

debugger invoked on a SB-INT:CLOSED-STREAM-ERROR in thread
#:
  # is closed

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-KERNEL:CLOSED-FLAME #)
0] 
(find-package :cl)
(find-package #:cl)
(find-package "CL")

Tak, tak; to jest prawdziwy strumień i po zamknięciu nie wolno już z niego czytać. No to teraz pisanie

* (defparameter s2 (make-string-output-stream))
S2
* (princ 17 s2)
17
* (princ "ala" s2)
"ala"
* (get-output-stream-string s2)
"17ala"
* (get-output-stream-string s2)
""
Strumień jest dalej czynny (nie było close), ale pozbył się zawartości. Natomiast funkcja
(make-string-output-stream)
tworzy strumień wyjściowy oparty na napisie. Wypisywane rzeczy będą trafiały do tego napisu.

Zawartość napisu można odczytać funkcją get-output-stream-string zwracającą napis. Dodatkowo zainicjuje ona strumień tak, by był pusty.

* (let ((in (make-string-input-stream "Hello"))
        (out (make-string-output-stream)))
    (prin1 (read in) out)
    (get-output-stream-string out))
"HELLO"

To było jednak dość prymitywne. Ze strumieniami związane są wygodne konstrukcje with-.

Konstrukcja

(with-input-from-string (zmienna napis [:index place]
                         [:start indeks-1] [:end indeks-2])
  deklaracje wyrażenie ...)
tworzy i otwiera strumień czytający znaki z napisu, wiążąc go z podaną zmienną lokalną. Następnie oblicza kolejno wyrażenia, po czym zamyka automatycznie strumień. Zwraca wartość ostatniego wyrażenia.
* (with-input-from-string (s1 "abc def")
    (read s1))
ABC

Symbol s1 staje się nazwą strumienia, automatycznie zamykanego po wykonaniu treści -- nawet po błędzie.

Jeśli podano argument :index, to po normalnym zakończeniu wpisany zostanie tam indeks pierwszego nie wczytanego znaku lub długość napisu, jeśli wczytano wszystkie znaki.

Opcjonalne argumenty :start i :end ograniczają czytanie do podnapisu zaczynającego się pierwszym z nich i kończącego tuż przed drugim. Domyślne wartości to 0 i długość napisu.

Przykład

* (let (next)
    (list
      (with-input-from-string (in "a b c d" :index next :start 3)
        (do ((x (read in nil nil) (read in nil nil)) (rlist nil))
            ((null x) rlist)
          (setq rlist (cons x rlist))))
      next))
((D C) 7)

Konstrukcja with-output-to-string jest bardziej skomplikowana.

(with-output-to-string (zmienna [napis])
  wyrażenie ...)

Obejrzyjmy najpierw prostszą postać

* (with-output-to-string (s2)
    (princ 17 s2)
    (princ "ala" s2))
"17ala"

Ze zmienną s2 związujemy napisowy strumień wyjściowy i obliczamy wyrażenia. Zwracamy wartość napisu z tego strumienia.

Jeśli natomiast podano opcjonalny napis, to powinien mieć fill-pointer. Wypisywane znaki są dopisywane do niego (podobnie do vector-push-extend lub vector-push). Zwracana jest wartość ostatniego wyrażenia z treści (czyli niekoniecznie ten napis).

* (defparameter string1
    (make-array 6 :fill-pointer t
                  :initial-contents '(#\a #\b #\c #\d #\e #\f)
                  :element-type 'character))
STRING1

* string1
"abcdef"

* (setf (fill-pointer string1) 2)
2

* (with-output-to-string (out string1)
    (do ((i 5 (- i 1)))
        ((zerop i) nil)
      (format out "~D " (* i i))))
NIL

* string1
"ab25 16 9 4 1 "
Jak widać, SBCL domyślnie zrobił napis adjustable. Ale nie liczcie na to w przenośnych programach -- lepiej zadeklarować
* (defparameter string2
    (make-array 6 :fill-pointer t
                  :initial-contents '(#\a #\b #\c #\d #\e #\f)
                  :element-type 'string-char
                  :adjustable t))
STRING2

Jak zrobić strumień dwukierunkowy? Z dwóch strumieni jednokierunkowych funkcją

(make-two-way-stream strwej strwyj)
Zwraca ona strumień dwukierunkowy czytający ze strwej i piszący na strwyj
* (let* ((in (make-string-input-stream "Hello"))
         (out (make-string-output-stream))
         (io (make-two-way-stream in out)))
    (prin1 (read io) io)
    (get-output-stream-string out))
"HELLO"

Jak pisać równocześnie na plik i terminal?

Niektóre funkcje piszą na standardowe wyjście. Można je przekierować na plik. Kontakt z terminalem zapewni nam bazowy strumień *terminal-io*, którego nie należy zmieniać.

(with-open-file (*standard-output* "my-file"
                                   :if-exists :overwrite
                                   :if-does-not-exist :create
                                   :direction :output)
  (print 1)
  (print 2 *standard-output*)
  (print 3 *terminal-io*)
  'finished) => finished

Jedynka i dwójka wypiszą się na plik, a trójka na terminal.

Pliki

Common Lisp ma własny typ do nazw plików: ścieżkę (pathname). Ścieżka ma szereg komponentów i jest niezależna od składni nazw plików w poszczególnych systemach operacyjnych. Otwierając plik możemy podać jego nazwę jako ścieżkę lub jako napis używany w systemie operacyjnym (namestring). Napis taki zostanie zamieniony na ścieżkę.

Podstawową funkcją do tworzenia ścieżek jest make-pathname (przy okazji obejrzymy składowe ścieżki

(make-pathname [:host h] [:device d] [:directory r] [:name n] [:type t]
               [:version v] [:defaults path]
Zwraca ona ścieżkę zbudowaną z podanych składowych. Dla pozostałych daje wartości domyślne (ze ścieżki domyślnej).

Domyślna ścieżka domyślna to wartość zmiennej *default-pathname-defaults*

* (let* ((x (make-pathname :name "foo" :host nil))
         (y (make-pathname :name "bar"))
         (mxy (merge-pathnames x y)))
    (list (pathname-name x) (pathname-host x)
          (pathname-name mxy) (pathname-host mxy)))
("foo" # "foo"
 #)

Mając nazwę pliku w postaci napisu można ją prościej zamienić na ścieżkę funkcją

(pathname napis)
Napis jest interpretowany jako namestring.
*  (with-open-file (in "~/.sbclrc")
      (list (streamp in)
            (pathnamep in)
            (pathnamep (pathname in))))
(T NIL T)

Czasem ścieżka jest niekompletna i chcemy ją uzupełnić brakującymi informacjami z innej ścieżki. Służy do tego funkcja merge-pathnames

(merge-pathnames ścieżka-1 [ścieżka-2])
Zwraca ona nową ścieżkę.

Dla większości komponentów zasady są proste: bierzemy z pierwszej jeśli tam jest, w przeciwnym razie uzupełniamy z drugiej. Ciekawiej jest dla części katalogowej.

Załóżmy, że łączymy dwie ścieżki, na przykład przez (merge-pathnames f1 f2). Jeśli nazwa f1 jest absolutna (zaczyna się od katalogu głównego) lub jeśli nazwa f2 jest względna, to bierzemy katalogi z f1.

Natomiast jeśli f1 jest nazwą względną, zaś f2 absolutną, to do ciągu katalogów z f2 doklejamy ciąg z f1.

Wartością zmiennej globalnej *default-pathname-defaults* jest ścieżka zawierająca domyślne składniki ścieżki. Jest ona używana przez funkcje plikowe, gdy otrzymają niekompletną ścieżkę, do jej uzupełnienia. Przykład

* (setq *default-pathname-defaults*
       (make-pathname :directory "tools"))
#p"tools/"

* (equalp (pathname-directory
            (merge-pathnames
              (make-pathname :name "fun" :type "lisp")))
          (pathname-directory *default-pathname-defaults*))
T

Funkcja truename zwraca dla podanej nazwy lub strumienia plikowego nazwę kanoniczną pliku: zwykle będzie to ścieżka absolutna.

(truename obiekt)
Argument może być pathname, napisem, symbolem albo strumieniem, dostaniemy odpowiadającą mu w pełni wyspecyfikowaną pathname istniejącego pliku.
* (truename "~zbyszek/.sbclrc")
#p"/usr/staff/inf/zbyszek/.sbclrc"
* (pathnamep (truename "foo"))
ERROR  ;; ponieważ plik nie istnieje
Możemy na przykład przekonać się, co Lisp uznaje za nasz katalog domowy:
* (user-homedir-pathname)
#P"/home/zbyszek/"
* (truename (user-homedir-pathname))
#P"/home/zbyszek/"
Funkcja probe-file sprawdza, czy podany plik istnieje. Jeśli tak, zwraca jego nazwę kanoniczną.

Funkcja

(file-position strumień> [pozycja])
podaje lub ustawia bieżące położenie w stumieniu plikowym.

Jeśli podano tylko jeden argument, zwracana jest wartość bieżącego położenia, o ile jest znana. Dla plików znakowych nie musi ona być równa liczbie wczytanych lub wypisanych znaków (znaki wielobajtowe, konwersja). Jeśli nie można określić bieżącego położenia, to wynikiem jest nil.

Drugi argument służy do ustawiania bieżącego położenia. Może być liczbą albo jednym z symboli :start lub :end. Wynikiem jest t jeśli udało się ustawić nowe położenie, w przeciwnym razie nil.

Przykład

* (with-open-file (out "junk" :direction :output
                       :if-exists :supersede)
    (format out "~A ~A" 'foo 'bar)
    ;; Ustawiamy się na `r' w `bar'
    (file-position out 6)
    (format out "~A" 'zar))
NIL

* (with-open-file (in "junk" :direction :input)
    ;; verify what the file contains by doing a read
    (list (read in) (file-position in) (read in)))
(FOO 4 BAZAR)

Funkcja

(delete-file )
usuwa plik podany nazwą lub otwartym strumieniem plikowym.

Funkcja

(file-length strumień-plikowy)
zwraca rozmiar otwartego pliku, z którym związany jest strumień, o ile taka informacja jest dostępna. W przeciwnym razie zwrana nil.

Rozmiar dla plików binarnych jest mierzony w jednostkach typu, z jakim otwarto strumień (parameter :element-type dla open). Przykład

* (with-open-file (out "junk" :direction :output
                       :if-exists :supersede)
    (format out "~A ~A" 'foo 'bar))
NIL
* (with-open-file (in "junk" :direction :input)
    (file-length in))
7

Funkcja rename-file zmienia nazwę pliku. Uwaga: ma trzy wyniki, pierwszy bywa dziwaczny

* (rename-file "./bar.txt" "foo.txt")
#P"/home/zbyszek/lisp/clman/./foo.txt"
#P"/home/zbyszek/lisp/clman/bar.txt"
#P"/home/zbyszek/lisp/clman/foo.txt"

Katalogi

Katalogi są obsługiwane dość skąpo.

Funkcja

(directory pathname)
zwraca listę nazw plików pasujących do podanej pathname. Zwykle używana się jej dla bieżącego katalogu.
* (mapcar #'file-namestring
          (directory (make-pathname :name "prog" :type :wild)))
("prog.fasl" "prog.cl")

* (directory (merge-pathnames "*.*" (user-homedir-pathname)))
(#P"/home/zbyszek/.ICEauthority" #P"/home/zbyszek/.PlayOnLinux/"
 #P"/home/zbyszek/.PlayOnLinux/wineprefix/" #P"/home/zbyszek/.Xauthority"
 #P"/home/zbyszek/.Xdefaults" #P"/home/zbyszek/.Xdefaults~"
 #P"/home/zbyszek/.bash_aliases" #P"/home/zbyszek/.bash_history"
...
Funkcja
(directory-namestring pathname)
zwraca napis odpowiadający części katalogowej pathname. Argument jak zwykle może być pathname, napisem, symbolem lub strumieniem plikowym.
* (directory-namestring "~/.bashrc")
"~/"

* (truename (directory-namestring "~/.bashrc"))
#P"/home/zbyszek/"

Podobna funkcja

(file-namestring pathname)
zwraca napis zawierający nazwę, typ i wersję zawartą w pathname.

Argument może być pathname, napisem, symbolem lub strumieniem otwartym na plik.

Przykład

* (file-namestring (make-pathname :host "katastrofa4"
                                  :directory "/etc"
                                  :name "host" :type "conf"))
"host.conf"

Pisanie

Mechanizm wyjścia zwany popularnie printerem generuje tekstową reprezentację obiektów Lisp, po czym drukuje ją na strumieniu wyjściowym. W zasadzie odpowiada on standardowej funkcji write. Sa dwie kategorie reprezentacji tekstowej: wczytywalna i nie. Reprezentacja wczytywalna może być wczytana przez Lisp i wyprodukować jakiś obiekt Lispu. Obiekt ten będzie równy (w sensie equal) oryginalnemu obiektowi. Niewczytywalna reprezentacja w takiej sytuacji spowoduje błąd. Niektóre obiekty nie mają reprezentacji wczytywalnej (np. readtables). Reprezentacja niewczytywalna takich obiektów ma postać
#<...>

Takie twory nie mogą być wczytane

* (defparameter f1 (open "work1.lisp"))

F1
* f1

#<SB-SYS:FD-STREAM for "file /home/zbyszek/robocza/lisp-lab/work1.lisp" {1003B47023}>
* (format t "~a" f1)
#<FD-STREAM for "file /home/zbyszek/robocza/lisp-lab/work1.lisp" {1003B47023}>
NIL
* (format t "~s" f1)
#<SB-SYS:FD-STREAM for "file /home/zbyszek/robocza/lisp-lab/work1.lisp" {1003B47023}>
NIL
* #<SB-SYS:FD-STREAM for "file /home/zbyszek/robocza/lisp-lab/work1.lisp" {1003B47023}>

debugger invoked on a SB-INT:SIMPLE-READER-ERROR in thread
#<THREAD "main thread" RUNNING {10005605B3}>:
  illegal sharp macro character: #\<

    Stream: #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDIN* {1000028FA3}>

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-IMPL::SHARP-ILLEGAL # #\< #)
0]                                         
Trudno oczekiwać, żeby nastepnym razem mieć otwarty ten plik (bo może go nie być).

Funkcja pprint wypisuje ,,ładnie'' argument, uprzednio przeszedłszy do nowej linii. Tak jak inne funkcje I/O, ma opcjonalny argument: strumień wyjściowy. Default nil oznacza *standard-output*. T oznacza *terminal-io*.

Funkcja format o prawie nieograniczonych możliwościach pozwala dokładnie sterować wypisywaniem obiektu na strumień.

(format strumień szablon arg ...)
Jeśli strumień jest NIL, to format zwraca napis. Strumień t to skrót od *standard-output*. Jeśli strumień jest napisem mającym fill-pointer, to znaki są dopisywane na końcu tego napisu.

Szablon zawiera elementy sterujące wypisywaniem (dyrektywy) -- pojedyncze znaki poprzedzane znakiem tyldy '~'. Większość dyrektyw zużywa dostarczone argumenty. Poza dyrektywami inne znaki są kopiowane na wyjście. Duże i małe znaki w nazwach dyrektyw są utożsamiane.

Pomiędzy tyldą a znakiem dyrektywy mogą wystąpić rozmaite prefiksy, modyfikujące podstawowe działanie dyrektywy.

Najprostsze dyrektywy

O jej bardziej zaawansowanych możliwościach wolicie zapewne na razie nie wiedzieć. Na wszelki wypadek lista wszystkich dyrektyw

~B wypisz integer binarnie
~C wypisz znak
~D wypisz integer dziesiętnie
~E wypisz liczbę rzeczywistą w notacji z wykładnikiem
~F wypisz liczbę rzeczywistą w notacji fixed-format
~G wypisz liczbę rzeczywistą
~O wypisz integer oktalnie
~P wypisz końcówkę liczby mnogiej lub pojedynczej (angielską)
~R wypisz integer w podanej podstawie
~T wypisz spacje do podanej kolumny (tabulacja)
~X wypisz integer szesnastkowo
~newline ignoruj znak nowej linii i spacje po nim
~$ wypisz liczbę rzeczywistą w notacji fixed-format dla dolarów
~% wypisz znak nowej linii
~& wypisz znak nowej linii jeśli bieżąca linia nie jest pusta
~(...~) konwersja pocztu litern
~* do przodu lub do tyłu po liście argumentów
~<...~;...~>justyfikacja tekstu
~? wypisz rekurencyjnie
~[...~;...~]wypisz warunkowo
~^ przerwanie dyrektywy złożonej
~{...~} wypisz iteracyjnie
~| wypisz znak nowej strony
~~ wypisz znak tyldy

Przykłady

* (format nil "~B" 64)
"1000000"

* (format nil "~20D" 1234567890)
"          1234567890"

* (format nil "~:@D" 1234567890)
"+1,234,567,890"

* (format nil "Time ~D fl~:@P like ~R arrow~:P." 3 1)
"Time 3 flies like one arrow."

Dyrektywa warunkowa wybiera jedną z możliwości. Najprostszy przypadek

* (format t "7 ~:[nie jest;jest] liczbą pierwszą.~%" (czy-pierwsza? 7))
7 jest liczbą pierwszą.
Znak `;' rozdziela alternatywne teksty: pierwszy jest używany gdy odpowiadający argument jest fałszem (nil), drugi w przeciwnym przypadku.

Przy iteracji argument musi być listą. Treść dyrektywy jest przetwarzana dla każdego kolejnego elementu listy.

* (format t "~&Nazwa~20TRozszerzenie~{~&~A~20T~A~}~%"
          '("Scheme" "scm" "Lisp" "lisp" "Prolog" "pro"))
Nazwa               Rozszerzenie
Scheme              scm
Lisp                lisp
Prolog              pro
NIL

Niektórzy nie lubią formatu, bo to całkowicie odrębny język. Ale czasem sporo ułatwia.

Konfigurowalne czytanie

Czytanie w Common Lispie jest konfigurowalne: można zmieniać znaczenie poszczególnych znaków wejściowych. Prostszy sposób to zmiana kategorii syntaktycznej znaku, bardziej ogólny to związanie ze znakiem własnej funkcji, wywoływanej po napotkaniu znaku podczas czytania. Nazywamy to makrem czytania (read-macro.

Funkcja get-macro-character opisuje zachowanie podanego znaku podczas czytania.

(get-macro-character znak [tablica-czytania])

Zwraca ona dwie wartości:

Przykłady (z SBCL):
* (get-macro-character #\()
SB-IMPL::READ-LIST
NIL

* (get-macro-character #\))
SB-IMPL::READ-RIGHT-PAREN
NIL

Funkcja set-macro-character definiuje znak jako makro czytania.

(set-macro-character znak funkcja [non-terminating? [tablica-czytania])

Po napotkaniu tego znaku podczas czytania zostanie wywołana podana funkcja. Definicja dotyczy tylko podanej tablicy, domyślnie bieżącej.

Argument non-terminating? różny od nil wymusza definicję nonterminating read makra. Podczas czytania taki znak zostanie uznany za makro tylko, gdy będzie rozpoczynał kolejny token.

Funkcja związana z makrem czytania ma dwa argumenty: strumień wejściowy i znak. Może ona na własną rękę czytać dodatkowe znaki. Zwraca 0 lub 1 wartość. Nie powinna powodować żadnych innych efektów ubocznych, bo może być wołana wielokrotnie w zagnieżdżonych kontekstach.

Jeśli nie zwróci żadnej wartości to makro czytania i wszystkie przez nią wczytane znaki są ignorowane. W przeciwnym razie zwrócona wartość jest jest traktowana przez reader tak, jakby została normalnie wczytana z wejścia.

Przykład: wczytywanie wektora otoczonego nawiasami kwadratowymi

* (defun read-bracketed-vector (stream character)
    (declare (ignore character))
    (apply #'vector (read-delimited-list #\] stream t)))
READ-BRACKETED-VECTOR

* (set-macro-character #\] (get-macro-character #\) nil))
T

* (set-macro-character #\[ #'read-bracketed-vector nil)
T

* '[1 2 3]
#(1 2 3)

* '[1 [2 [3]]]
#(1 #(2 #(3)))

* (vectorp '[a b])
t

Procesem czytania steruje bieżąca tablica czytania.

Funkcja copy-readtable

copy-readtable [tablica-źródłowa [tablica-docelowa]]
wydaje się prosta -- kopiuje tablicę czytania. Dla jednego argumentu zwraca kopię tablicy źródłowej. Jeśli parameterm tym jest nil, to kopiuje się standardową tablicę czytania Common Lispu.

Jeśli nie podano parametru, to brana jest wartość zmiennej globalnej *readtable*: bieżącej tablicy czytania.

Gdy podano dwa parametry, funkcja robi się destrukcyjna. Nadpisuje tablicę docelową zawartością tablicy źródłowej.