NAME

Sidef::Types::Glob::File - File manipulation and information retrieval

DESCRIPTION

This class provides an interface for file operations in Sidef: reading, writing, metadata retrieval, and filesystem manipulation. It's a subclass of Sidef::Types::String::String -- a File object is really just a path string with file-specific methods layered on top, so ordinary string methods work on it too.

Nearly every method here also works when called directly on the File class itself (e.g. File.exists("/tmp/x")), not just on an instance -- the implementation shifts the class off the argument list and treats the next argument as the path.

SYNOPSIS

var file = File("example.txt")

# Create and write to a file
file.write("Hello, World!")

# Read file contents
say(file.read)

# Check file properties
if (file.exists && file.is_readable) {
    say("File exists and is readable")
}

# Get file information
say("Size: #{file.size} bytes")
say("Directory: #{file.dirname}")
say("Basename: #{file.basename}")

# Copy and move files
file.copy("backup.txt")
file.move("new_location.txt")

# Work with paths
var abs_path = file.abs_path
say("Absolute path: #{abs_path}")

INHERITS

Inherits methods from Sidef::Types::String::String.

CONSTRUCTION

new

File(path)
File(*path_components)
File.new(path)

Creates a new File object. With a single argument, path is used as-is (stringified first, if it isn't already a plain string). With more than one argument, the arguments are joined as path components (via File::Spec::catfile, so the platform's own directory separator is used) into a single path.

var file = File.new("/path/to/file.txt")
var file2 = File("/path/to/file.txt")          # same as above
var file3 = File("path", "to", "file.txt")     # joined: "path/to/file.txt"

Aliases: call

PATH COMPONENTS

name

file.name

Returns the file's path exactly as given (or as joined, if constructed from multiple components), as a String -- this does not make it absolute or otherwise normalize it.

var file = File("/home/user/file.txt")
say(file.name)                   #=> "/home/user/file.txt"

basename

file.basename

Returns just the filename portion of the path (without any directory component), like the Unix basename command.

var file = File("/home/user/document.txt")
say(file.basename)               #=> "document.txt"

Aliases: base, base_name

dirname

file.dirname

Returns the directory portion of the path, as a Dir object (not a plain String), like the Unix dirname command.

var file = File("/home/user/documents/file.txt")
say(file.dirname)                #=> Dir("/home/user/documents")

Aliases: dir, dir_name

split

file.split

Splits the path into its directory components (and filename), via File::Spec::splitdir, and returns them as an Array of Strings.

var file = File("/home/user/docs/file.txt")
say(file.split)                  #=> ["", "home", "user", "docs", "file.txt"]

splitpath

file.splitpath

Splits the path into volume, directory, and filename components, via File::Spec::splitpath, and returns them as a 3-element Array.

var file = File("/home/user/file.txt")
var (vol, dir, name) = file.splitpath...
# vol: "", dir: "/home/user/", name: "file.txt"

is_absolute

file.is_absolute

Returns true if the path is absolute, false if it's relative.

say(File("/home/user/file.txt").is_absolute)   #=> true
say(File("relative/path.txt").is_absolute)     #=> false

Aliases: is_abs

abs_name

file.abs_name(base=Dir.cwd)

Returns a new File with the path resolved to an absolute one, using base (or the current working directory, if omitted) for any relative component. This does not resolve symlinks or ./.. segments -- see "abs_path" for that.

var file = File("docs/readme.txt")
say(file.abs_name)                 # e.g. "/home/user/docs/readme.txt"
say(file.abs_name("/var/www"))     #=> "/var/www/docs/readme.txt"

Aliases: abs, absname, rel2abs

rel_name

file.rel_name(base=Dir.cwd)

Computes a path relative to base.

var file = File("/home/user/docs/file.txt")
say(file.rel_name("/home/user"))   # returns "docs/file.txt"

Aliases: rel, relname, abs2rel

abs_path

file.abs_path

Returns a new File with the canonical absolute path: symlinks, ., and .. are all resolved, giving the real physical path on the filesystem.

var file = File("../project/link_to_file")
say(file.abs_path)               # e.g. "/home/user/project/actual_file"

Aliases: realpath

FILE TYPE TESTS

exists

file.exists

Returns true if something exists at the path (of any type -- regular file, directory, symlink, etc.), otherwise false.

File("data.txt").exists || die("File not found")

is_file

file.is_file

Returns true if the path is a plain (regular) file, otherwise false.

File("document.txt").is_file && say("Regular file")

is_directory

file.is_directory

Returns true if the path is a directory, otherwise false.

File("/home/user").is_directory && say("This is a directory")

Aliases: is_dir

file.is_link

Returns true if the path is a symbolic link, false otherwise (including if the filesystem doesn't support symlinks at all).

File("shortcut").is_link && say("This is a symlink")

is_socket

file.is_socket

Returns true if the path is a Unix domain socket.

File("/var/run/socket").is_socket && say("This is a socket")

is_block

file.is_block

Returns true if the path is a block special file (block device).

File("/dev/sda1").is_block && say("Block device")

is_char_device

file.is_char_device

Returns true if the path is a character special file (character device).

File("/dev/tty").is_char_device && say("Character device")

is_empty

file.is_empty

Returns true if the file has zero size.

File("empty.txt").is_empty && say("File is empty")

is_binary

file.is_binary

Returns true if the file's contents heuristically look binary (Perl's own -B test: a probe of the file's opening bytes).

File("image.png").is_binary && say("Binary file detected")

is_text

file.is_text

Returns true if the file's contents heuristically look like ASCII/UTF-8 text (Perl's own -T test; the logical opposite of "is_binary" for most files).

File("readme.txt").is_text && say("Text file detected")

PERMISSIONS AND OWNERSHIP

Each pair below (is_X / is_real_X) checks the same permission bit, but against different user/group IDs: the plain form uses the process's effective UID/GID (what actually governs access, and can differ under setuid/setgid programs), while the real_ form uses the process's real UID/GID (who actually invoked it).

is_readable

file.is_readable

Returns true if the file is readable, checked against the effective UID/GID.

File("data.txt").is_readable || die("Cannot read file")

is_writeable

file.is_writeable

Returns true if the file is writable, checked against the effective UID/GID.

File("output.log").is_writeable || die("Cannot write to file")

is_executable

file.is_executable

Returns true if the file is executable, checked against the effective UID/GID.

File("run.sh").is_executable || die("Script is not executable")

is_owned

file.is_owned

Returns true if the file is owned by the effective UID of the current process.

File("myfile.txt").is_owned && say("You own this file")

is_real_readable

file.is_real_readable

Like "is_readable", but checked against the real (not effective) UID/GID.

is_real_writeable

file.is_real_writeable

Like "is_writeable", but checked against the real (not effective) UID/GID.

is_real_executable

file.is_real_executable

Like "is_executable", but checked against the real (not effective) UID/GID.

is_real_owned

file.is_real_owned

Returns true if the file is owned by the real (not effective) UID of the current process.

has_setuid_bit

file.has_setuid_bit

Returns true if the file's setuid bit is set (an executable with this bit runs with its owner's privileges).

File("/usr/bin/sudo").has_setuid_bit && say("Has setuid bit")

has_setgid_bit

file.has_setgid_bit

Returns true if the file's setgid bit is set (an executable with this bit runs with its group's privileges).

File("/usr/bin/wall").has_setgid_bit && say("Has setgid bit")

has_sticky_bit

file.has_sticky_bit

Returns true if the file's sticky bit is set.

File("/tmp").has_sticky_bit && say("Has sticky bit")

TIMESTAMPS

modification_time_days_diff

var days = File("foo.txt").modification_time_days_diff

Returns the number of days since the file was last modified.

The returned value is a fractional number, where the integer part represents whole days and the fractional part represents the elapsed portion of the current day.

This method is equivalent to Perl's -M file test operator.

Example:

say File("log.txt").modification_time_days_diff  #=> 2.34

access_time_days_diff

var days = File("foo.txt").access_time_days_diff

Returns the number of days since the file was last accessed.

The returned value is a fractional number, where the integer part represents whole days and the fractional part represents the elapsed portion of the current day.

This method is equivalent to Perl's -A file test operator.

Example:

say File("notes.txt").access_time_days_diff  #=> 0.08

change_time_days_diff

var days = File("foo.txt").change_time_days_diff

Returns the number of days since the file's inode status was last changed.

On Unix-like systems, this corresponds to the last metadata change (such as permissions, ownership, or renaming), and is not necessarily the same as the last modification of the file contents.

The returned value is a fractional number, where the integer part represents whole days and the fractional part represents the elapsed portion of the current day.

This method is equivalent to Perl's -C file test operator.

Example:

say File("script.pl").change_time_days_diff  #=> 1.75

utime

file.utime(atime, mtime)

Sets the file's access and modification times (as Unix timestamps). Returns true on success, false otherwise.

var now = Time.now.to_i
File("data.txt").utime(now, now)

stat

file.stat

Returns a Stat object with the file's metadata (size, permissions, timestamps, inode number, etc.). If the path is a symbolic link, this reports on the target, not the link -- see "lstat" for the link itself.

var info = File("data.txt").stat
say("Size: #{info.size}")

lstat

file.lstat

Like "stat", but if the path is a symbolic link, reports on the link itself rather than following it to its target.

READING AND WRITING CONTENT

size

file.size

Returns the size of the file in bytes.

say("File size: #{File("document.txt").size} bytes")

read

file.read(layer="utf8")

Reads and returns the entire contents of the file as a String, decoded through the given PerlIO layer (without a leading colon -- just the layer name itself, e.g. "utf8" or "raw"). Defaults to UTF-8. Returns nil if the file can't be opened.

var content = File("data.txt").read           # read as UTF-8 text
var binary  = File("data.bin").read("raw")     # read as raw bytes

write

file.write(string, layer="utf8")

Writes string to the file, replacing any existing content (creating the file if it doesn't exist), through the given PerlIO layer (again, no leading colon). Returns true on success, or nil/false otherwise -- not the File object itself, so this can't be chained.

File("output.txt").write("Hello, World!")
File("output.bin").write(binary_data, "raw")

append

file.append(string, layer="utf8")

Appends string to the end of the file (creating it if it doesn't exist), through the given PerlIO layer (no leading colon). Returns true on success, or nil/false otherwise -- not the File object itself.

File("log.txt").append("New log entry\n")

edit

file.edit(block)

Rewrites the file in place, one line at a time: block is called once per line (each as a String, including its line terminator), and the collected return values of every call replace the file's entire content. block does not receive the whole file at once.

File("text.txt").edit {|line| line.uc }

File("config.ini").edit {|line| line.gsub(/old_value/, "new_value") }

md5

file.md5

Returns the MD5 hash of the file's contents, as a hex string.

say("MD5: #{File("data.bin").md5}")

sha1

file.sha1

Returns the SHA-1 hash of the file's contents, as a hex string.

sha256

file.sha256

Returns the SHA-256 hash of the file's contents, as a hex string.

sha512

file.sha512

Returns the SHA-512 hash of the file's contents, as a hex string.

compare

file.compare(other)

Compares the byte contents of self and other. Returns 0 if identical, -1 if self sorts before other, 1 if after, or nil on error.

given (File("a.txt").compare(File("b.txt"))) {
    when (0)  { say("Files are identical") }
    default   { say("Files differ") }
}

OPENING FILES

open

file.open(mode="<:utf8", fh_ref=nil, err_ref=nil)

Opens the file with a raw Perl-style mode string (e.g. '<', ''>, '>'>, '+<', each optionally suffixed with an encoding layer like ':utf8'). If fh_ref is given, the resulting FileHandle is stored through that reference and this returns true/false for success (storing any error message through err_ref, if given); otherwise, it returns the FileHandle directly (or nil on failure).

var fh = nil
var err = nil
if (File("data.txt").open('<', \fh, \err)) {
    fh.close
} else {
    die(err)
}

open_r

file.open_r(*args)

Opens the file for reading (UTF-8). Equivalent to self.open('<:utf8', *args) -- see "open" for what args can do.

var fh = File("input.txt").open_r

Aliases: open_read

open_w

file.open_w(*args)

Opens the file for writing (UTF-8), truncating any existing content. Equivalent to self.open(':utf8', *args)>.

var fh = File("output.txt").open_w

Aliases: open_write

open_a

file.open_a(*args)

Opens the file for appending (UTF-8), creating it if needed. Equivalent to self.open('>:utf8', *args)>.

var fh = File("log.txt").open_a

Aliases: open_append

open_rw

file.open_rw(*args)

Opens the file for both reading and writing (UTF-8), without truncating. Equivalent to self.open('+<:utf8', *args).

Aliases: open_read_write

open_arw

file.open_arw(*args)

Opens the file for appending, reading, and writing (UTF-8). Equivalent to self.open('+>:utf8', *args)>.

Aliases: open_append_read_write

opendir

file.opendir(*args)

Treats the path as a directory and opens it (equivalent to Dir(self).open(*args)), returning a directory handle for reading entries.

var dh = File("/home/user").opendir
dh.each {|entry| say(entry) }

sysopen

file.sysopen(var_ref, mode, perm=0666)

Opens the file using Perl's low-level sysopen, with numeric mode flags (see "FCNTL CONSTANTS" below) and perm permission bits. Stores the resulting FileHandle through var_ref. Returns true on success, false otherwise.

var fh = nil
File("data.bin").sysopen(\fh, O_RDWR | O_CREAT, 0644)

touch

file.touch(*args)

Creates the file if it doesn't already exist (by opening it in append mode, via "open_a", and forwarding args the same way "open" does). For a file that already exists, this mostly leaves it untouched -- it does not reliably update the modification/access times the way the Unix touch command does.

File("newfile.txt").touch

Aliases: make, mkfile, create

mktemp

File.mktemp(%options)

Creates a new temporary file (via File::Temp) and returns an already-open FileHandle for it -- not a plain File object. %options are forwarded to File::Temp::tempfile (e.g. template and dir).

var tmp = File.mktemp
tmp.say("temporary data")

var tmp2 = File.mktemp(template => "myapp_XXXXXX", dir => "/var/tmp")

Aliases: make_tmp, make_temp

FCNTL CONSTANTS

Every constant exported by Perl's core Fcntl module (O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_APPEND, O_TRUNC, SEEK_SET, SEEK_CUR, SEEK_END, LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB, and so on) is exposed as a method on File that returns its value as a Number, computed and cached on first use. These are mainly useful as flags for "sysopen".

say(File.O_RDWR | File.O_CREAT)

FILESYSTEM OPERATIONS

rename

file.rename(new_name)

Renames the file on disk to new_name. Returns true on success, false otherwise. Note that self's own stored path is not updated to the new name -- construct a new File if you need to keep using it under the new name.

File("old.txt").rename("new.txt")

move

file.move(destination)

Moves the file to destination (a filename, or a File/Dir object). Returns true on success, false otherwise. As with "rename", self's own stored path is not updated.

File("old_name.txt").move("new_name.txt")
File("old_name.txt").move(Dir("archive"))

Aliases: mv

copy

file.copy(destination)

Copies the file to destination (a filename, or a File/Dir object). Returns true on success, false otherwise.

File("source.txt").copy("backup.txt")

Aliases: cp

file.link(new_name)

Creates a hard link named new_name pointing to the same inode as file. Returns true on success, false otherwise.

File("source.txt").link("hardlink.txt")
file.symlink(link_name)

Creates a symbolic link named link_name pointing to file (which doesn't need to exist yet). Returns true on success, false otherwise.

File("original.txt").symlink("link_to_original")
file.readlink

Reads the target of a symbolic link, returning it as a Dir object if the target is itself a directory, or as a File object otherwise. If the path isn't actually a symbolic link, the returned object wraps an undefined path rather than being nil outright.

var target = File("shortcut").readlink

Aliases: read_link

chown

file.chown(uid, gid)

Changes the file's owner and group (as numeric IDs). Returns true on success, false otherwise. May require elevated privileges.

File("data.txt").chown(1000, 1000)

chmod

file.chmod(permission)

Changes the file's permission bits. Returns true on success, false otherwise.

File("script.sh").chmod(0755)

truncate

file.truncate(length=0)

Truncates the file to length bytes (extending with null bytes if the file was shorter). Returns true on success, false otherwise.

File("data.txt").truncate(100)
file.unlink
File.unlink(*paths)

Called on an instance, deletes just that file and returns true/false. Called on the File class itself with a list of paths, deletes each one and returns the Number of files successfully deleted (not a boolean).

File("temp.txt").unlink                     #=> true or false
File.unlink("file1.txt", "file2.txt")       #=> a count, e.g. 2

delete

file.delete
File.delete(*paths)

Like "unlink", but called on an instance, returns true/false; called on the class with a list of paths, returns the count of files successfully deleted.

File("temporary.txt").delete && say("File deleted successfully")

Aliases: remove

CONVERSION

to_str

file.to_str

Returns the file's path as a plain String.

say(File("/home/user/file.txt").to_str)   #=> "/home/user/file.txt"

Aliases: to_s

dump

file.dump

Returns a String representation of the File object for debugging, in the format File("path").

say(File("example.txt").dump)   #=> File("example.txt")

to_file

file.to_file

Returns self unchanged. Useful in polymorphic code that might receive either a plain String or an already-constructed File object and wants to normalize to the latter.

get_value

file.get_value

Returns the raw underlying path string (unwrapped from the File object). A low-level method, mainly useful for interop rather than everyday Sidef code.

EXAMPLES

Basic file operations

var file = File("example.txt")
file.write("Line 1\nLine 2\nLine 3\n")

say(file.read)
file.append("Line 4\n")

say("Size: #{file.size} bytes")
say("Readable: #{file.is_readable}")
say("Writable: #{file.is_writeable}")

File information and metadata

var file = File("/etc/passwd")

say("Directory: #{file.dirname}")
say("Basename: #{file.basename}")
say("Absolute path: #{file.abs_path}")

var info = file.stat
say("Inode: #{info.ino}")
say("Owner UID: #{info.uid}")

Checksums

var file = File("download.iso")

say("MD5:    #{file.md5}")
say("SHA256: #{file.sha256}")

Copying, moving, and linking

var source = File("original.txt")

source.copy("backup.txt")
source.move("archive/original.txt")

File("data.txt").link("hardlink.txt")
File("data.txt").symlink("symlink.txt")

In-place, line-by-line editing

# Uppercase every line
File("text.txt").edit {|line| line.uc }

# Replace a pattern on every line
File("config.ini").edit {|line| line.gsub(/old_value/, "new_value") }

Working with file handles

var fh = File("large.txt").open_r
fh.each_line {|line| say(line.trim) }
fh.close

var out = File("output.txt").open_w
out.say("Header")
10.times {|i| out.say("Line #{i}") }
out.close

Temporary files

var tmp = File.mktemp
tmp.say("temporary data")
tmp.close

File type checking

var path = File(ARGV[0])

given (path) {
    when { .is_file }   { say("Regular file") }
    when { .is_dir }    { say("Directory") }
    when { .is_link }   { say("Symbolic link -> #{path.readlink}") }
    when { .is_socket } { say("Unix socket") }
    default              { say("Unknown file type") }
}

SEE ALSO

Sidef::Types::Glob::Dir, Sidef::Types::Glob::FileHandle, Sidef::Types::String::String