Skip to content

File

An open file.

Returned by open and Path.open. Every operation on a closed file raises StateError.

A file is both an iterator and a sink, so it can be used with for, .next(), .put(), and strand.redirect. The open mode fixes which for the file's lifetime: a text-mode file yields and accepts lines, a binary-mode one arbitrary chunks. In either mode the values read from a file concatenate back to exactly the file's bytes.

Inherits from: Iter[Str | Bin], Sink[Value]

Methods

(iter)() -> File

Returns the file as its own iterator.

(next)() -> (Str | Bin)

Fetches the next value from the file.

In text mode this reads the next line as a Str, including its terminator, so a \r\n file stays \r\n and a final line without one yields a value without one. Use chomp to strip them:

for line = file.chomp()
  echo $line

In binary mode it reads a chunk of data of arbitrary length.

(put) value

Writes a value to the file, verbatim.

A Str or Bin contributes its own bytes and nothing else, and anything else is converted to a string first. No line ending is appended in either mode, and none is translated.

Use precrimp to terminate written values, with shell.line_ending() for the target's native ending:

open $path w do |file|
  let lines = file.precrimp()
  lines.put "first"
  lines.put "second"

Parameters

NameTypeDescription
value Value to write.

(sink)() -> File

Returns the file as its own sink.

acl … -> (security.AnyAcl | nil)

Gets the ACL stored on the open file.

Parameters

NameTypeDescription
:kind? (:POSIX: | :NFS4: | :MACOS:) ACL format to query. Defaults to :POSIX:.
:default? Bool Query the directory's inheritable default ACL.

Returns

The matching portable ACL type, depending on kind, or nil when no ACL metadata is stored.

close()

Closes the file.

This is required if open was not given a block. Closing a file that is already closed does nothing.

Passing a file to a child process as stdin:, stdout:, or stderr: also closes it: the child receives the file itself, and the seek position is kept by this process rather than by the operating system, so two live handles would each believe a cursor the other moves. Use the file after the handoff and it raises the ordinary closed-file error.

Example

let file = open data.txt r
let data = file.read()
file.close()

copy_data dst … -> Int

The method form of fs.copy_data.

Parameters

NameTypeDescription
dst File Handle to write to.
:range? Range[Int] Absolute byte region of the source.
:size? Int Number of bytes to take from the source cursor.
:offset? Int Absolute byte offset in the destination.
:clone? CloneMode How blocks should be shared between the source and destination. Defaults to :AUTO:.

fs_metadata() -> FsMetadata

Gets filesystem metadata for the filesystem backing the open file.

Example

open data.txt r do |file|
  let meta = file.fs_metadata()
  echo "Available: $(meta.available)"

lock[R] range func … -> R

Acquires a byte-range lock while func runs.

Parameters

NameTypeDescription
range Range[Int] Half-open byte range; .. is the whole file.
func ((FileLock) -> R) Block receiving a FileLock.
:shared? Bool Acquire a shared rather than exclusive lock.

Returns

The block's result.

The lock is exclusive unless shared is true. It is released before the method returns, including when the block raises an error or is canceled. Release runs with interruption masked and may wait indefinitely.

Locks are mandatory on Windows, where they prevent conflicting file access. On Unix they are advisory and affect only programs that cooperate through file locking.

Native blocking acquisition cannot be canceled. Canceling the strand may leave a blocking worker waiting for a conflicting lock and delay shutdown. Use try_lock for bounded or cancellable waiting.

Overlapping active lock ranges on the same file are unsupported, including identical shared ranges.

Finite zero-length ranges are invalid on Unix. On Windows they conflict only with positive-length ranges that start before and end after their offset. They do not conflict with another zero-length range or a range starting at the same offset. The zero-length range 0..0 is invalid.

Example

file.lock (0..128) do |lock|
  update_header()

metadata() -> Metadata

Gets metadata for the open file.

Example

open data.txt r do |file|
  let meta = file.metadata()
  echo "Size: $(meta.size)"
  echo "Modified: $(meta.modified)"

read … -> (Str | Bin)

Reads data from the file.

Parameters

NameTypeDescription
size? Int Number of bytes to read. Without it, reads to the end of the file.
:offset? Int Byte offset to read from. Without it, reads at the cursor and advances it.
size

size bytes are read however many transfers that takes; a shorter result means the end of the file was reached, and reading entirely past the end gives an empty result rather than an error.

:offset

A positional read leaves the cursor where it was, so it can be used alongside streaming reads on the same handle, and several regions of a file can be read without seeking between them.

Because a positional read touches nothing shared, any number of them may be in flight on one handle at once — several strands can read different regions of the same open file concurrently. Streaming reads still take the handle exclusively, since they move the cursor.

In text mode the bytes read must be complete UTF-8. There is no cursor for a positional read to carry a split character forward on, so unlike a streaming read it cannot hold a partial character back for next time.

Returns

A Str in text mode, or a Bin in binary mode.

Example

# Read entire file
open input.txt r do |file|
  let content = file.read()
  echo "File contents: $content"

# Read a specific number of bytes
open data.bin rb do |file|
  let header = file.read 4
  let rest = file.read()

# Read a record without moving the cursor
open data.bin rb do |file|
  let record = file.read 64 offset: (index * 64)

remove_xattr name …

Removes an extended attribute.

Parameters

NameTypeDescription
name (Str | XattrEntry) Attribute name or entry from xattrs.
:namespace? XattrNamespace Namespace to update.

Example

open data.txt r+ do |file|
  file.remove_xattr "comment"

sec_desc … -> security.windows.SecDesc

Gets selected parts of the Windows security descriptor through this file's existing handle.

The operation raises a permission error if the file was opened without the necessary Windows access rights. Other platforms raise sys.UnsupportedError.

Parameters

NameTypeDescription
:owner? Bool Load the owner SID. Defaults to true.
:group? Bool Load the primary group SID. Defaults to true.
:dacl? Bool Load the discretionary ACL. Defaults to true.
:sacl? Bool Load the system ACL. Defaults to false.

seek … -> Int

Moves the file cursor.

Buffered unread data is discarded before the seek so subsequent reads use the new cursor position.

Parameters

NameTypeDescription
offset? Int Byte offset relative to the current position.
:start? Int Absolute byte offset from the start of the file.
:end? Int Byte offset relative to the end of the file.

Returns

The new absolute byte position.

Example

open data.bin rb do |file|
  file.seek start: 10   # absolute
  file.seek 10          # relative to the cursor
  file.seek (0 - 4)     # backwards
  file.seek end: (0 - 1)  # relative to the end

set_acl acl …

Sets or removes an ACL on the open file.

A built ACL supplies its format and must match an explicit kind:. An untyped sequence of declarative ACE dictionaries requires kind:. With nil, the omitted kind remains POSIX.

Parameters

NameTypeDescription
acl (security.AclSpec | nil) ACL or declarative ACE sequence.
:kind? (:POSIX: | :NFS4: | :MACOS:) Required for an untyped ACL specification.
:default? Bool Update the directory's inheritable default ACL.

set_size size

Truncates the file to the given byte length.

If the file has buffered unread data, the logical cursor position is preserved after truncation.

Parameters

NameTypeDescription
size Int New file length in bytes.

Example

open data.bin r+ do |file|
  file.set_size 8

set_xattr name value …

Sets an extended attribute value.

On Windows, empty values are rejected. NTFS deletes the attribute instead of storing an empty value.

Parameters

NameTypeDescription
name (Str | XattrEntry) Attribute name or entry from xattrs.
value (Str | Bin) Attribute bytes; strings use UTF-8.
:namespace? XattrNamespace Namespace to update.

Example

open data.txt r+ do |file|
  file.set_xattr "comment" "ready"

streams() -> Iter[fs.windows.StreamEntry]

Lists alternate data streams for this file.

Windows only.

Example

let path = Path data.txt
open $path r do |file|
  for stream = file.streams()
    echo (path / stream)

sync …

Flushes the file to durable storage, returning once the device reports it committed.

Parameters

NameTypeDescription
:data? Bool Flush data only, skipping unneeded metadata. See fs.sync for what this selects and what a flush does and does not guarantee.

Example

open journal.bin w do |file|
  file.write $entry
  file.sync()

tell() -> Int

Returns the current cursor position in bytes.

Example

open data.txt r do |file|
  assert_eq (file.tell()) 0
  file.read 5
  assert_eq (file.tell()) 5

try_lock[R] range func … -> R

Attempts to acquire a byte-range lock without waiting.

Parameters

NameTypeDescription
range Range[Int] Half-open byte range; .. is the whole file.
func ((FileLock) -> R) Block receiving a FileLock.
:shared? Bool Acquire a shared rather than exclusive lock.

Returns

The block's result.

The block always runs. lock.held is false when another handle holds a conflicting lock. Other acquisition failures raise errors.

Example

file.try_lock (..) do |lock|
  if lock.held
    update_index()

update_sec_desc ...options …

Applies the components selected by a security descriptor's mask through this file's existing handle.

The operation raises a permission error if the file was opened without the necessary Windows access rights. Windows may normalize the resulting descriptor. Other platforms raise sys.UnsupportedError.

Parameters

NameTypeDescription
desc? security.windows.SecDescSpec Descriptor to apply.
...options Components, as sec_desc takes them, instead of or alongside desc.

write data … -> Int

Writes data to the file.

All of data is written, however many transfers that takes.

Parameters

NameTypeDescription
data (Str | Bin) Data to write. Strings are written as UTF-8 text.
:offset? Int Byte offset to write at. Without it, writes at the cursor and advances it.
:offset

A positional write leaves the cursor where it was, so it can be used alongside streaming writes on the same handle, and several regions of a file can be written without seeking between them. Writing past the end extends the file, zero-filling the gap.

As with read, any number of positional writes may be in flight on one handle at once.

Returns

The number of bytes written.

Errors

Exception Condition
StateError offset: on a file opened for appending

An append handle writes at the end of the file no matter what offset the platform is given, so an explicit one cannot be honored rather than merely being unimplemented.

Example

open output.txt w do |file|
  let bytes_written = file.write "Hello, World!"
  echo "Wrote $bytes_written bytes"
  file.write b"Hello"

# Patch a record in place without disturbing the cursor
open data.bin r+b do |file|
  file.write $record offset: (index * 64)

xattr name … -> Bin

Gets an extended attribute value.

Parameters

NameTypeDescription
name (Str | XattrEntry) Attribute name or entry from xattrs.
:namespace? XattrNamespace Namespace to query.

Example

open data.txt r do |file|
  let value = file.xattr "comment"

xattrs … -> Iter[XattrEntry]

Lists extended attributes for this file.

On Windows, this uses NTFS extended attributes. Returned names may differ in case from the requested name.

Parameters

NameTypeDescription
:namespace? (XattrNamespace | :ANY:) Namespace to query; :ANY: lists all namespaces.

Example

open data.txt r do |file|
  for attr = file.xattrs()
    echo $attr.name