Reputation: 33599
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
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
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