rwallace
rwallace

Reputation: 33599

Open a file, or do something else if the file does not exist

I want to open a file and read the contents, or do something else if the file does not exist.

The former can be accomplished easily enough: with-open-file.

For the latter, my first thought was to use handler-case to catch the error, but SBCL says it's SB-INT:SIMPLE-FILE-ERROR, which sounds like a compiler internal symbol, therefore presumably nonportable.

What's the portable way to do this?

Upvotes: 2

Views: 1260

Answers (3)

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 10010

If to create simply:

(with-open-file (foo "path/to/non-existing-file" :direction :output
                                                 :if-does-not-exist :create)
  ;; do your duty on stream `foo`)

If do sth else let foo be bound to nil and give instructions for this case:

 (with-open-file (foo "no-such-file" :direction :output :if-does-not-exist nil)
   (when foo 
     ;; if file exists do what you intended
     ;; explicitely return a value with `(return ...)`
     )
   (unless foo 
     ;; catch the case that `foo` is bound to nil
     ;; so these are instructions for the `file doesn't exist` case 
     ))

If file does not exist, nil is bound to foo. One could test then for nil and knows whether opening file was successful or not.

Upvotes: 2

coredump
coredump

Reputation: 38967

Use :if-does-not-exist nil (see OPEN):

(defun test (path)
  (with-open-file (stream path
                          :if-does-not-exist nil
                          :element-type '(unsigned-byte 8))
    (if stream
        (read-byte stream)
        :something-else)))

With a non-existent pathname:

(test #P"/hopefully/path/does/not/exist")
=> :SOMETHING-ELSE

With an existing pathname:

(test #P"/dev/urandom")
=> 123

You can also use PROBE-FILE to check whether a file exists, as Rainer explained, but then you risk having the file being deleted by another process, after probe-file returns successfully but before actually opening it.

For the latter, my first thought was to use handler-case to catch the error, but SBCL says it's SB-INT:SIMPLE-FILE-ERROR, which sounds like a compiler internal symbol, therefore presumably nonportable.

When you catch an implementation-specific error, you can try to inspect its class hierarchy to find the closest superclass whose name belongs to the Common-Lisp package:

CL-USER> (inspect (find-class 'SB-INT:SIMPLE-FILE-ERROR)) 

The object is a STANDARD-OBJECT of type SB-PCL::CONDITION-CLASS.
0. %TYPE: (CLASS #<SB-PCL::CONDITION-CLASS SB-INT:SIMPLE-FILE-ERROR>)
...
5. DIRECT-SUPERCLASSES: (#<SB-PCL::CONDITION-CLASS COMMON-LISP:SIMPLE-CONDITION>
                         #<SB-PCL::CONDITION-CLASS COMMON-LISP:FILE-ERROR>)
6. DIRECT-SUBCLASSES: NIL
...
11. %CLASS-PRECEDENCE-LIST: (#<SB-PCL::CONDITION-CLASS SB-INT:SIMPLE-FILE-ERROR>
                             #<SB-PCL::CONDITION-CLASS COMMON-LISP:SIMPLE-CONDITION>
                             #<SB-PCL::CONDITION-CLASS COMMON-LISP:FILE-ERROR>
                             #<SB-PCL::CONDITION-CLASS COMMON-LISP:ERROR>
                             #<SB-PCL::CONDITION-CLASS COMMON-LISP:SERIOUS-CONDITION>
                             #<SB-PCL::CONDITION-CLASS COMMON-LISP:CONDITION>
                             #<SB-PCL::SLOT-CLASS SB-PCL::SLOT-OBJECT>
                             #<SB-PCL:SYSTEM-CLASS COMMON-LISP:T>)
...

What the above says is that in SBCL, SB-INT:SIMPLE-FILE-ERROR has two direct superclasses, one of them being COMMON-LISP:FILE-ERROR.

Here is another example, using CCL:

? (handler-case (open #P"/tmp/foo/bar/baz/foo") (error (e) (inspect e)))

[0]     #<CCL::SIMPLE-FILE-ERROR #x3020004DE7ED>
[1]     Class: #<STANDARD-CLASS CCL::SIMPLE-FILE-ERROR>
[2]     Wrapper: #<CCL::CLASS-WRAPPER CCL::SIMPLE-FILE-ERROR #x3020000F62DD>
        Instance slots
[3]     PATHNAME: #P"/tmp/foo/bar/baz/foo"
[4]     CCL::ERROR-TYPE: "No such file or directory : ~s"
[5]     CCL::FORMAT-CONTROL: #<Unbound>
[6]     CCL::FORMAT-ARGUMENTS: (NIL)
Inspect> 1
[0]     #<STANDARD-CLASS CCL::SIMPLE-FILE-ERROR>
[1]     Class: #<STANDARD-CLASS STANDARD-CLASS>
...
[6]     CCL::PRECEDENCE-LIST: (#<STANDARD-CLASS CCL::SIMPLE-FILE-ERROR>
                       #<STANDARD-CLASS SIMPLE-CONDITION>
                       #<STANDARD-CLASS FILE-ERROR> #<STANDARD-CLASS ERROR>
                       #<STANDARD-CLASS SERIOUS-CONDITION> ...)
...
[8]     CCL::DIRECT-SUPERCLASSES: (#<STANDARD-CLASS SIMPLE-CONDITION>
                           #<STANDARD-CLASS FILE-ERROR>)
...

Upvotes: 9

Svante
Svante

Reputation: 51541

The portable error type to handle for this case is file-error (see CLHS).

Upvotes: 5

Related Questions