Class: File::Stat
| Relationships & Source Files | |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Instance Chain:
self,
::Comparable
|
|
| Inherits: | Object |
| Defined in: | file.c, file.c |
Overview
A File::Stat object contains information about an entry in the filesystem.
Each of these methods returns a new File::Stat object. the first three follow symbolic links; the others don't:
Snapshot
A new File::Stat object takes an immediate "snapshot" of the filesystem entry at the given 'path'; the snapshot is never updated, regardless of changes in the entry (even its deletion):
filepath = '/tmp/t.tmp'
File.stat(filepath) # Raises Errno::ENOENT: No such file or directory.
File.write(filepath, 'foo')
stat = File.stat(filepath)
stat.birthtime # => 2026-10-03 12:19:23.723062465 -0500
File.delete(filepath)
stat.birthtime # => 2026-10-03 12:19:23.723062465 -0500
Filesystem Dependencies
Methods in a File::Stat object may return filesystem-dependent values,
and not all values are meaningful on all filesystems;
for example, #blocks returns nil on a Windows filesystem,
but returns an integer on others.
See also Kernel.test.
Class Method Summary
- .new(path) ⇒ Stat constructor
Instance Attribute Summary
-
#blockdev? ⇒ Boolean
readonly
Returns whether the entry in thesnapshot in
selfis a block device (i.e., a direct-access device): -
#chardev? ⇒ Boolean
readonly
Returns whether the entry in thesnapshot in
selfis a character device (i.e., a sequential-access device): -
#directory? ⇒ Boolean
readonly
Returns whether the entry in thesnapshot in
selfis a directory: -
#executable? ⇒ Boolean
readonly
Returns whether the filesystem entry represented by
selfisexecutable by the effective owner/group of the current process: -
#executable_real? ⇒ Boolean
readonly
Like Pathname#executable?, but checks against the real owner/group instead of the effective owner/group.
-
#file? ⇒ Boolean
readonly
Returns whether
selfrepresents a filesystem entry that exists and is a regular file; see ftype: -
#grpowned?(path) ⇒ Boolean
readonly
Returns
falseon Windows. -
#owned? ⇒ Boolean
readonly
Returns whether the owner in thesnapshot in
selfis the owner of the current process: -
#pipe? ⇒ Boolean
readonly
Returns whether the entry at the path in
selfis a pipe: -
#readable? ⇒ Boolean
readonly
Returns whether the entry represented by
selfexists and is readable by the owner and group of the current process; seePermissions: -
#readable_real? ⇒ Boolean
readonly
Like #readable?, but checks against the real owner and group ids instead of the effective ids.
-
#setgid? ⇒ Boolean
readonly
Returns whether the setgid bit is set in thespecial bits for the entry represented in
self: -
#setuid? ⇒ Boolean
readonly
Returns whether the setuid bit is set in thespecial bits for the entry represented in
self: -
#size ⇒ Integer
readonly
Returns the size (in bytes) of the entry in thesnapshot in
self: -
#size? ⇒ Boolean
readonly
Returns the size in bytes of the entry in
selfif the entry exists and has non-zero size,nilotherwise: -
#socket? ⇒ Boolean
readonly
Returns whether entry in thesnapshot in
selfis a socket: -
#sticky? ⇒ Boolean
readonly
Returns whether the sticky bit is set in thespecial bits for
self: -
#symlink? ⇒ Boolean
readonly
Returns whether the entry in
self(seeSnapshot) is asymbolic link: -
#world_readable? ⇒ Boolean
readonly
Returns the integerpermissions for the entry in thesnapshot in
selfif those permissions allow reading by others; otherwise, returnsnil: -
#world_writable? ⇒ Boolean
readonly
If the entry in
selfexists and is writable by others, returns the integerpermissions for the entry; otherwise, returnsnil: -
#writable? ⇒ Boolean
readonly
Returns whether the entry at the path in
selfexists and is writable by the effective owner and group in the current process: -
#writable_real? ⇒ Boolean
readonly
Like File::Stat.writable?, but checks against the real owner and group instead of the effective owner and group.
-
#zero? ⇒ Boolean
readonly
Returns whether the entry at the path in
selfhas size zero.
Instance Method Summary
-
#<=>(other) ⇒ 1, ...
Compares thesnapshots in
selfandother, by comparing their modification timesself.mtimeandother.mtime. -
#atime ⇒ Time
Returns a new
::Timeobject containing the access time of thesnapshot inself. See File System Timestamps. -
#birthtime ⇒ Time
Returns a new
::Timeobject containing the creation time of thesnapshot inself. See File System Timestamps: -
#blksize ⇒ Integer?
Returns the block size of the filesystem of thesnapshot in
self, ornilon Windows: -
#blocks ⇒ Integer?
Returns the number of filesystem blocks allocated for the entry in thesnapshot in
self: -
#ctime ⇒ Time
On Windows, returns the birthtime for the entry in
self. -
#dev ⇒ Integer
Returns the device number for thesnapshot in
self: -
#dev_major ⇒ Integer
Returns
nilon Windows. -
#dev_minor ⇒ Integer
Returns
nilon Windows. -
#ftype ⇒ String
Returns the string type of the path in thesnapshot in
self, one of: -
#gid ⇒ Integer
On Windows, returns
0. -
#ino ⇒ Integer
Returns the inode value for the entry in thesnapshot in
self: -
#inspect ⇒ String
Returns a string representation of the entry in thesnapshot in
self; here we split the returned strings for readability: -
#mode ⇒ Integer
Returns an integer representing the permissions of the entry in thesnapshot in
self; seeFilesystem Modes. -
#mtime ⇒ Time
Returns a new
::Timeobject containing themodification time of the object in thesnapshot inself: -
#nlink ⇒ Integer
Returns the number ofhard links to the entry in thesnapshot in
self: -
#rdev ⇒ Integer?
Returns the integer device type of the entry in thesnapshot in
self. - #rdev_major ⇒ Integer
- #rdev_minor ⇒ Integer
-
#uid ⇒ Integer
Returns the integer id of the owner of the entry in thesnapshot in
self: - #initialize_copy(orig) Internal use only
::Comparable - Included
| #< | Returns whether |
| #<= | Returns whether |
| #== | Compares two objects based on the receiver's #<=> method, returning true if it returns 0. |
| #> | Returns whether |
| #>= | Returns whether |
| #between? | |
| #clamp | Returns an object of the same type as |
Constructor Details
.new(path) ⇒ Stat
# File 'file.c', line 7107
static VALUE
rb_stat_init(VALUE obj, VALUE fname)
{
rb_io_stat_data st;
FilePathValue(fname);
fname = rb_str_encode_ospath(fname);
if (STATX(StringValueCStr(fname), &st, STATX_ALL) == -1) {
rb_sys_fail_path(fname);
}
struct rb_stat *rb_st;
TypedData_Get_Struct(obj, struct rb_stat, &stat_data_type, rb_st);
rb_st->stat = st;
rb_st->initialized = true;
return Qnil;
}
Instance Attribute Details
#blockdev? ⇒ Boolean (readonly)
Returns whether the entry in thesnapshot in self
is a block device (i.e., a direct-access device):
File.stat('/dev/nvme0n1').blockdev? # => true
File.stat('/dev/loop0').blockdev? # => true
File.stat('/dev/tty').blockdev? # => false
File.stat('/dev/null').blockdev? # => false
The returned value is filesystem-dependent; returns false on Windows or MacOS.
# File 'file.c', line 7333
static VALUE
rb_stat_b(VALUE obj)
{
#ifdef S_ISBLK
if (S_ISBLK(get_stat(obj)->ST_(mode))) return Qtrue;
#endif
return Qfalse;
}
#chardev? ⇒ Boolean (readonly)
Returns whether the entry in thesnapshot in self
is a character device (i.e., a sequential-access device):
File.stat('/dev/tty').chardev? # => true
File.stat('/dev/null').chardev? # => true
File.stat('/dev/nvme0n1').chardev? # => false
File.stat('/dev/loop0').chardev? # => false
The returned value is filesystem-dependent; on Windows, always false.
# File 'file.c', line 7362
static VALUE
rb_stat_c(VALUE obj)
{
if (S_ISCHR(get_stat(obj)->ST_(mode))) return Qtrue;
return Qfalse;
}
#directory? ⇒ Boolean (readonly)
Returns whether the entry in thesnapshot in self
is a directory:
File.stat('/etc').directory? # => true
File.stat('/etc/passwd').directory? # => false
# File 'file.c', line 7206
static VALUE
rb_stat_d(VALUE obj)
{
if (S_ISDIR(get_stat(obj)->ST_(mode))) return Qtrue;
return Qfalse;
}
#executable? ⇒ Boolean (readonly)
Returns whether the filesystem entry represented by self
isexecutable
by the effective owner/group of the current process:
File.stat('/bin/ruby').executable? # => true
File.stat('/etc').executable? # => true
File.stat('/etc/passwd').executable? # => false
Raises Errno::ENOENT if the entry does not exist.
Note that some filesystem settings may cause this method to return true
even though the entry is not executable by the effective owner/group.
# File 'file.c', line 7690
static VALUE
rb_stat_x(VALUE obj)
{
rb_io_stat_data *st = get_stat(obj);
#ifdef USE_GETEUID
if (geteuid() == 0) {
return RBOOL(st->ST_(mode) & S_IXUGO);
}
#endif
#ifdef S_IXUSR
if (rb_stat_owned(obj))
return RBOOL(st->ST_(mode) & S_IXUSR);
#endif
#ifdef S_IXGRP
if (rb_stat_grpowned(obj))
return RBOOL(st->ST_(mode) & S_IXGRP);
#endif
#ifdef S_IXOTH
if (!(st->ST_(mode) & S_IXOTH)) return Qfalse;
#endif
return Qtrue;
}
#executable_real? ⇒ Boolean (readonly)
Like Pathname#executable?, but checks against the real owner/group instead of the effective owner/group.
# File 'file.c', line 7723
static VALUE
rb_stat_X(VALUE obj)
{
rb_io_stat_data *st = get_stat(obj);
#ifdef USE_GETEUID
if (getuid() == 0) {
return RBOOL(st->ST_(mode) & S_IXUGO);
}
#endif
#ifdef S_IXUSR
if (rb_stat_rowned(obj))
return RBOOL(st->ST_(mode) & S_IXUSR);
#endif
#ifdef S_IXGRP
if (rb_group_member(get_stat(obj)->ST_(gid)))
return RBOOL(st->ST_(mode) & S_IXGRP);
#endif
#ifdef S_IXOTH
if (!(st->ST_(mode) & S_IXOTH)) return Qfalse;
#endif
return Qtrue;
}
#file? ⇒ Boolean (readonly)
# File 'file.c', line 7763
static VALUE
rb_stat_f(VALUE obj)
{
if (S_ISREG(get_stat(obj)->ST_(mode))) return Qtrue;
return Qfalse;
}
#grpowned?(path) ⇒ Boolean (readonly)
Returns false on Windows.
On other systems, returns whether the effective group id of the calling process
is the owner of thesnapshot in self:
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
File.stat(filepath).grpowned? # => true
File.stat('/etc').grpowned? # => false
File.stat('/etc/passwd').grpowned? # => false
File.delete(filepath) # Clean up.
# File 'file.c', line 7431
static VALUE
rb_stat_grpowned(VALUE obj)
{
#ifndef _WIN32
if (rb_group_member(get_stat(obj)->ST_(gid))) return Qtrue;
#endif
return Qfalse;
}
#owned? ⇒ Boolean (readonly)
Returns whether the owner in thesnapshot in self
is the owner of the current process:
dirpath = '/tmp/dir'
Dir.mkdir(dirpath)
filepath = File.join(dirpath, 't.tmp') # => "/tmp/dir/t.tmp"
File.write(filepath, 'foo')
dirstat = File.stat(dirpath) # Snapshot of directory.
filestat = File.stat(filepath) # Snapshot of file.
dirstat.owned? # => true
filestat.owned? # => true
FileUtils.rm_rf(dirpath) # Clean up.
dirstat.owned? # => true # Snapshot not updated.
filestat.owned? # => true # Snapshot not updated.
# File 'file.c', line 7395
static VALUE
rb_stat_owned(VALUE obj)
{
if (get_stat(obj)->ST_(uid) == geteuid()) return Qtrue;
return Qfalse;
}
#pipe? ⇒ Boolean (readonly)
Returns whether the entry at the path in self is a pipe:
pipepath = '/tmp/pipe'
File.mkfifo(pipepath) # Make pipe.
stat = File.stat(pipepath) # Take snapshot.
stat.pipe? # => true # In snapshot.
File.delete(pipepath) # Clean up.
stat.pipe? # => true # Snapshot not updated.
File.stat('/etc').pipe? # => false
File.stat('/etc/passwd').pipe? # => false
# File 'file.c', line 7234
static VALUE
rb_stat_p(VALUE obj)
{
#ifdef S_IFIFO
if (S_ISFIFO(get_stat(obj)->ST_(mode))) return Qtrue;
#endif
return Qfalse;
}
#readable? ⇒ Boolean (readonly)
Returns whether the entry represented by self
exists and is readable by the owner and group of the current process;
seePermissions:
path = '/tmp/secret.txt'
File.write(path, 'foo')
File.stat(path).readable? # => true
File.chmod(0o000, path)
File.stat(path).readable? # => false
File.delete(path) # Clean up.
# File 'file.c', line 7461
static VALUE
rb_stat_r(VALUE obj)
{
rb_io_stat_data *st = get_stat(obj);
#ifdef USE_GETEUID
if (geteuid() == 0) return Qtrue;
#endif
#ifdef S_IRUSR
if (rb_stat_owned(obj))
return RBOOL(st->ST_(mode) & S_IRUSR);
#endif
#ifdef S_IRGRP
if (rb_stat_grpowned(obj))
return RBOOL(st->ST_(mode) & S_IRGRP);
#endif
#ifdef S_IROTH
if (!(st->ST_(mode) & S_IROTH)) return Qfalse;
#endif
return Qtrue;
}
#readable_real? ⇒ Boolean (readonly)
Like #readable?, but checks against the real owner and group ids instead of the effective ids.
# File 'file.c', line 7493
static VALUE
rb_stat_R(VALUE obj)
{
rb_io_stat_data *st = get_stat(obj);
#ifdef USE_GETEUID
if (getuid() == 0) return Qtrue;
#endif
#ifdef S_IRUSR
if (rb_stat_rowned(obj))
return RBOOL(st->ST_(mode) & S_IRUSR);
#endif
#ifdef S_IRGRP
if (rb_group_member(get_stat(obj)->ST_(gid)))
return RBOOL(st->ST_(mode) & S_IRGRP);
#endif
#ifdef S_IROTH
if (!(st->ST_(mode) & S_IROTH)) return Qfalse;
#endif
return Qtrue;
}
#setgid? ⇒ Boolean (readonly)
Returns whether the setgid bit is set
in thespecial bits
for the entry represented in self:
path = '/tmp/t.tmp'
File.write(path, 'foo')
stat = File.stat(path) # Take a snapshot.
stat.setgid? # => false
stat.mode.to_s(8) # => "100664"
File.chmod(0o2644, path) # Set the bit; stat snapshot unchanged.
stat.setgid? # => false
stat.mode.to_s(8) # => "100664"
stat = File.stat(path) # Fresh stat; snapshot changed.
stat.setgid? # => true
stat.mode.to_s(8) # => "102644"
File.delete(path) # Clean up.
On Windows, the bit is never set; the method always returns false.
# File 'file.c', line 7914
static VALUE
rb_stat_sgid(VALUE obj)
{
#ifdef S_ISGID
if (get_stat(obj)->ST_(mode) & S_ISGID) return Qtrue;
#endif
return Qfalse;
}
#setuid? ⇒ Boolean (readonly)
Returns whether the setuid bit is set
in thespecial bits
for the entry represented in self:
path = '/tmp/t.tmp'
File.write(path, 'foo')
stat = File.stat(path) # Take snapshot; bit not set.
stat.setuid? # => false
stat.mode.to_s(8) # => "100664"
File.chmod(0o4644, path) # Set the bit; snapshot not updated.
stat.setuid? # => false
stat.mode.to_s(8) # => "100664"
stat = File.stat(path) # Fresh snapshot.
stat.setuid? # => true
stat.mode.to_s(8) # => "104644"
File.delete(path) # Clean up.
On Windows, the bit is never set; the method always returns false.
# File 'file.c', line 7877
static VALUE
rb_stat_suid(VALUE obj)
{
#ifdef S_ISUID
if (get_stat(obj)->ST_(mode) & S_ISUID) return Qtrue;
#endif
return Qfalse;
}
#size ⇒ Integer (readonly)
Returns the size (in bytes) of the entry
in thesnapshot in self:
File.stat('/etc/passwd').size # => 3092 # Regular file.
File.stat('/etc').size # => 12288 # Directory.
#### When the file size changes.
filepath = '/tmp/t.tmp'
file = File.new(filepath, 'w+')
file.write('foo')
file.size # => 3
stat0 = File.stat(filepath) # Take snapshot.
stat0.size # => 3
file.write('bar') # Change file size.
file.size # => 6
stat0.size # => 3 # Snapshot not updated.
stat1 = File.stat(filepath) # Take new snapshot.
stat1.size # => 6 # Snapshot has updated size.
File.delete(filepath) # Clean up.
# File 'file.c', line 1097
static VALUE
rb_stat_size(VALUE self)
{
return OFFT2NUM(get_stat(self)->ST_(size));
}
#size? ⇒ Boolean (readonly)
Returns the size in bytes of the entry in self
if the entry exists and has non-zero size, nil otherwise:
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
File.size(filepath) # => 3
stat = File.stat(filepath) # Take snapshot.
stat.size? # => 3 # Non-zero size.
File.write(filepath, '')
File.size(filepath) # => 0
stat.size? # => 3 # Snapshot unchanged.
stat = File.stat(filepath) # Take new snapshot.
stat.size? # => nil # Zero size
File.delete(filepath) # Clean up.
# File 'file.c', line 7840
static VALUE
rb_stat_s(VALUE obj)
{
rb_off_t size = get_stat(obj)->ST_(size);
if (size == 0) return Qnil;
return OFFT2NUM(size);
}
#socket? ⇒ Boolean (readonly)
Returns whether entry in thesnapshot
in self is a socket:
require 'socket'
socket_path = '/tmp/socket'
server = UNIXServer.new(socket_path)
stat = File.stat(socket_path) # Take snapshot.
stat.socket? # => true
server.close # Clean up.
File.delete(socket_path) # Clean up.
stat.socket? # => true # Snapshot not updated.
File.stat('/tmp').socket? # => false # Not a socket.
# File 'file.c', line 7303
static VALUE
rb_stat_S(VALUE obj)
{
#ifdef S_ISSOCK
if (S_ISSOCK(get_stat(obj)->ST_(mode))) return Qtrue;
#endif
return Qfalse;
}
#sticky? ⇒ Boolean (readonly)
Returns whether the sticky bit is set
in thespecial bits
for self:
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
stat = File.stat(filepath)
stat.sticky? # => false
stat.mode.to_s(8) # => "100664"
File.chmod(01644, filepath) # => 1 # Stat unchanged.
stat.sticky? # => false
stat.mode.to_s(8) # => "100664"
stat = File.stat(filepath) # Fresh stat.
stat.sticky? # => true
stat.mode.to_s(8) # => "101644"
File.delete(filepath) # Clean up.
Returns false on Windows.
# File 'file.c', line 7951
static VALUE
rb_stat_sticky(VALUE obj)
{
#ifdef S_ISVTX
if (get_stat(obj)->ST_(mode) & S_ISVTX) return Qtrue;
#endif
return Qfalse;
}
#symlink? ⇒ Boolean (readonly)
Returns whether the entry in self (seeSnapshot)
is asymbolic link:
filepath = '/etc/passwd'
linkpath = '/tmp/foo'
File.symlink(filepath, linkpath)
File.stat(filepath).symlink? # => false
File.lstat(filepath).symlink? # => false
stat = File.stat(linkpath) # Snapshot with stat follows link.
stat.symlink? # => false
lstat = File.lstat(linkpath) # Snapshot with lstat does not follow link.
lstat.symlink? # => true
File.delete(linkpath) # Clean up.
#### Snapshots are unchanged, even when link deleted.
stat.symlink? # => false
lstat.symlink? # => true
# File 'file.c', line 7271
static VALUE
rb_stat_l(VALUE obj)
{
#ifdef S_ISLNK
if (S_ISLNK(get_stat(obj)->ST_(mode))) return Qtrue;
#endif
return Qfalse;
}
#world_readable? ⇒ Boolean (readonly)
Returns the integerpermissions
for the entry in thesnapshot in self
if those permissions allow reading by others;
otherwise, returns nil:
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo') # Create file.
File.chmod(0o000, filepath) # Make file unreadable.
stat0 = File.stat(filepath) # Take snapshot.
stat0.world_readable? # => nil # Snapshot shows not world-readable.
File.chmod(0o644, filepath) # Make file world-readable.
stat0.world_readable? # => nil # Snapshot not updated.
stat1 = File.stat(filepath) # Take new snapshot.
stat1.world_readable?.to_s(8) # => "644" # Snapshot shows world-readable.
File.delete(filepath) # Clean up.
stat1.world_readable?.to_s(8) # => "644" # Snapshot not updated.
# File 'file.c', line 7541
static VALUE
rb_stat_wr(VALUE obj)
{
#ifdef S_IROTH
rb_io_stat_data *st = get_stat(obj);
if ((st->ST_(mode) & (S_IROTH)) == S_IROTH) {
return UINT2NUM(st->ST_(mode) & (S_IRUGO|S_IWUGO|S_IXUGO));
}
#endif
return Qnil;
}
#world_writable? ⇒ Boolean (readonly)
If the entry in self exists and is writable by others,
returns the integerpermissions
for the entry;
otherwise, returns nil:
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
File.stat(filepath).world_writable? # => nil # Not world-writable.
File.chmod(0o777, filepath) # Make world-writable.
File.stat(filepath).world_writable?.to_s(8) # => "777" # World-writable.
File.delete(filepath) # Clean up.
File.stat('/tmp').world_writable?.to_s(8) # => "777" # Directory.
# File 'file.c', line 7656
static VALUE
rb_stat_ww(VALUE obj)
{
#ifdef S_IWOTH
rb_io_stat_data *st = get_stat(obj);
if ((st->ST_(mode) & (S_IWOTH)) == S_IWOTH) {
return UINT2NUM(st->ST_(mode) & (S_IRUGO|S_IWUGO|S_IXUGO));
}
#endif
return Qnil;
}
#writable? ⇒ Boolean (readonly)
Returns whether the entry at the path in self exists and is writable
by the effective owner and group in the current process:
filepath = '/tmp/secret.txt'
File.write(filepath, 'foo')
File.stat(filepath).writable? # => true # Writable.
File.chmod(0o000, filepath) # Make non-writable.
File.stat(filepath).writable? # => false # Not writable.
File.delete(filepath) # Clean up.
File.stat('/etc').writable? # => false # Directory.
Note that filesystem security features may cause this method to return true
even when the file is not writable by the effective owner and group.
# File 'file.c', line 7576
static VALUE
rb_stat_w(VALUE obj)
{
rb_io_stat_data *st = get_stat(obj);
#ifdef USE_GETEUID
if (geteuid() == 0) return Qtrue;
#endif
#ifdef S_IWUSR
if (rb_stat_owned(obj))
return RBOOL(st->ST_(mode) & S_IWUSR);
#endif
#ifdef S_IWGRP
if (rb_stat_grpowned(obj))
return RBOOL(st->ST_(mode) & S_IWGRP);
#endif
#ifdef S_IWOTH
if (!(st->ST_(mode) & S_IWOTH)) return Qfalse;
#endif
return Qtrue;
}
#writable_real? ⇒ Boolean (readonly)
Like File::Stat.writable?, but checks against the real owner and group instead of the effective owner and group.
Note that filesystem security features may cause this method to return true
even when the entry in self is not writable by the real owner and group.
# File 'file.c', line 7611
static VALUE
rb_stat_W(VALUE obj)
{
rb_io_stat_data *st = get_stat(obj);
#ifdef USE_GETEUID
if (getuid() == 0) return Qtrue;
#endif
#ifdef S_IWUSR
if (rb_stat_rowned(obj))
return RBOOL(st->ST_(mode) & S_IWUSR);
#endif
#ifdef S_IWGRP
if (rb_group_member(get_stat(obj)->ST_(gid)))
return RBOOL(st->ST_(mode) & S_IWGRP);
#endif
#ifdef S_IWOTH
if (!(st->ST_(mode) & S_IWOTH)) return Qfalse;
#endif
return Qtrue;
}
#zero? ⇒ Boolean (readonly)
Returns whether the entry at the path in self has size zero.
The entry may be a file:
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
File.stat(filepath).zero? # => false
File.truncate(filepath, 0)
File.stat(filepath).zero? # => true
File.delete(filepath) # Clean up.
The entry may be a directory:
dirpath = '/tmp/foo'
Dir.mkdir(dirpath)
stat = File.stat(dirpath)
#### Size is filesystem-dependent; may or may not be zero.
stat.size # => 4096
stat.zero? # => false
filepath = File.join(dirpath, 't.tmp') # => "/tmp/foo/t.tmp"
File.write(filepath, 'foo')
stat = File.stat(dirpath)
stat.size # => 4096
stat.zero? # => false
FileUtils.rm_rf(dirpath) # Clean up.
# File 'file.c', line 7808
static VALUE
rb_stat_z(VALUE obj)
{
if (get_stat(obj)->ST_(size) == 0) return Qtrue;
return Qfalse;
}
Instance Method Details
#<=>(other) ⇒ 1, ...
Compares thesnapshots
in self and other, by comparing their modification times
self.mtime and other.mtime.
Returns:
-1, ifself.mtimeis earlier.0, if the two values are equal.1, ifself.mtimeis later.nil, ifotheris not a File::Stat object.
Examples:
stat0 = File.stat('/etc')
stat1 = File.stat('/tmp')
stat0.mtime # => 2026-10-02 07:52:03.151556038 -0500
stat1.mtime # => 2026-10-03 12:22:25.719215899 -0500
stat0 <=> stat1 # => -1
stat0 <=> stat0.dup # => 0
stat1 <=> stat0 # => 1
stat0 <=> :foo # => nil
Class File::Stat includes module Comparable, each of whose methods uses File::Stat#<=> for comparison.
# File 'file.c', line 646
static VALUE
rb_stat_cmp(VALUE self, VALUE other)
{
if (rb_obj_is_kind_of(other, rb_obj_class(self))) {
stat_timestamp ts1 = statx_mtimespec(get_stat(self));
stat_timestamp ts2 = statx_mtimespec(get_stat(other));
if (ts1.tv_sec == ts2.tv_sec) {
if (ts1.tv_nsec == ts2.tv_nsec) return INT2FIX(0);
if (ts1.tv_nsec < ts2.tv_nsec) return INT2FIX(-1);
return INT2FIX(1);
}
if (ts1.tv_sec < ts2.tv_sec) return INT2FIX(-1);
return INT2FIX(1);
}
return Qnil;
}
#atime ⇒ Time
Returns a new ::Time object containing the access time
of thesnapshot in self.
See File System Timestamps.
Access time for a file is established when it is created, and may be updated when the file content is read:
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
File.atime(filepath) # => 2026-10-04 14:58:58.805179514 -0500
stat0 = File.stat(filepath) # Take snapshot.
stat0.atime # => 2026-10-04 14:58:58.805179514 -0500
File.read(filepath) # Updates file atime.
File.atime(filepath) # => 2026-10-04 14:59:47.707059351 -0500
stat0.atime # => 2026-10-04 14:58:58.805179514 -0500 # Not updated.
stat1 = File.stat(filepath) # Take new snapshot.
stat1.atime # => 2026-10-04 14:59:47.707059351 -0500 # Updated.
File.delete(filepath) # Clean up.
Access time for a directory is established when it is created, and may be updated when its entries are read:
dirpath = '/tmp/dir'
Dir.mkdir(dirpath)
File.atime(dirpath) # => 2026-10-04 15:07:26.194548647 -0500
stat0 = File.stat(dirpath) # Take snapshot.
stat0.atime # => 2026-10-04 15:07:26.194548647 -0500
Dir.entries(dirpath) # Updates directory atime.
File.atime(dirpath) # => 2026-10-04 15:08:14.818746815 -0500 # Updated.
stat0.atime # => 2026-10-04 15:07:26.194548647 -0500 # Not updated.
stat1 = File.stat(dirpath) # Take new shapshot.
stat1.atime # => 2026-10-04 15:08:14.818746815 -0500 # Updated.
Dir.rmdir(dirpath) # Clean up.
# File 'file.c', line 1318
static VALUE
rb_stat_atime(VALUE self)
{
return stat_time(statx_atimespec(get_stat(self)));
}
#birthtime ⇒ Time
Returns a new ::Time object containing the creation time
of thesnapshot in self.
See File System Timestamps:
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
stat = File.stat(filepath) # Take shapshot.
stat.birthtime # => 2026-10-05 13:14:24.844158315 -0500
File.delete(filepath)
stat.birthtime # => 2026-10-05 13:14:24.844158315 -0500 # Unchanged.
# File 'file.c', line 1417
static VALUE
rb_stat_birthtime(VALUE self)
{
return statx_birthtime(get_stat(self));
}
#blksize ⇒ Integer?
# File 'file.c', line 1121
static VALUE
rb_stat_blksize(VALUE self)
{
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
return ULONG2NUM(get_stat(self)->ST_(blksize));
#else
return Qnil;
#endif
}
#blocks ⇒ Integer?
# File 'file.c', line 1148
static VALUE
rb_stat_blocks(VALUE self)
{
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
# if SIZEOF_STRUCT_STAT_ST_BLOCKS > SIZEOF_LONG
return ULL2NUM(get_stat(self)->ST_(blocks));
# else
return ULONG2NUM(get_stat(self)->ST_(blocks));
# endif
#else
return Qnil;
#endif
}
#ctime ⇒ Time
On Windows, returns the birthtime for the entry in self.
On other systems, returns the time of the most recent metadata change
to thesnapshot in self:
dirpath = '/tmp/dir'
Dir.mkdir(dirpath) # Establishes directory ctime.
dirstat0 = File.stat(dirpath) # Take directory snapshot.
dirstat0.ctime # => 2026-10-06 11:07:18.073533542 -0500 # Initial directory ctime.
filepath = File.join(dirpath, 't.tmp') # => "/tmp/dir/t.tmp"
File.write(filepath, 'foo') # Establishes file ctime; upates directory ctime.
filestat0 = File.stat(filepath) # Take file snapshot.
filestat0.ctime # => 2026-10-06 11:07:52.555027213 -0500 # Initial file ctime.
dirstat0.ctime # => 2026-10-06 11:07:18.073533542 -0500 # Directory snapshot unchanged.
dirstat1 = File.stat(dirpath) # Take new directory snapshot.
dirstat1.ctime # => 2026-10-06 11:07:52.555027213 -0500 # Snapshot has new ctime.
File.write(filepath, 'bar') # Updates file ctime.
filestat0.ctime # => 2026-10-06 11:07:52.555027213 -0500 # Old file snapshot unchanged.
filestat1 = File.stat(filepath) # Take new file snapshot.
filestat1.ctime # => 2026-10-06 11:14:11.19290223 -0500 # Snapshot shows change.
FileUtils.rmtree(dirpath) # Clean up.
# File 'file.c', line 1389
static VALUE
rb_stat_ctime(VALUE self)
{
return stat_time(statx_ctimespec(get_stat(self)));
}
#dev ⇒ Integer
# File 'file.c', line 690
static VALUE
rb_stat_dev(VALUE self)
{
#if RUBY_USE_STATX
unsigned int m = get_stat(self)->stx_dev_major;
unsigned int n = get_stat(self)->stx_dev_minor;
return ULL2NUM(makedev(m, n));
#elif SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_DEV_T
return DEVT2NUM(get_stat(self)->st_dev);
#elif SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_LONG
return ULONG2NUM(get_stat(self)->st_dev);
#else
return ULL2NUM(get_stat(self)->st_dev);
#endif
}
#dev_major ⇒ Integer
# File 'file.c', line 724
static VALUE
rb_stat_dev_major(VALUE self)
{
#if RUBY_USE_STATX
return UINT2NUM(get_stat(self)->stx_dev_major);
#elif defined(major)
return UINT2NUM(major(get_stat(self)->st_dev));
#else
return Qnil;
#endif
}
#dev_minor ⇒ Integer
# File 'file.c', line 754
static VALUE
rb_stat_dev_minor(VALUE self)
{
#if RUBY_USE_STATX
return UINT2NUM(get_stat(self)->stx_dev_minor);
#elif defined(minor)
return UINT2NUM(minor(get_stat(self)->st_dev));
#else
return Qnil;
#endif
}
#ftype ⇒ String
Returns the string type of the path in thesnapshot
in self, one of:
'file'.'directory'.'characterSpecial'.'blockSpecial'.'fifo'.'link'.'socket'.
Examples:
File.stat('/etc/passwd').ftype # => "file"
File.stat('/etc').ftype # => "directory"
File.stat('/dev/null').ftype # => "characterSpecial"
File.stat('/dev/loop0').ftype # => "blockSpecial"
File.mkfifo('/tmp/pipe', 0o666)
File.stat('/tmp/pipe').ftype # => "fifo"
File.symlink('/etc/passwd', '/tmp/link')
File.lstat('/tmp/link').ftype # => "link"
require 'socket'
UNIXServer.new('/tmp/socket')
File.stat('/tmp/socket').ftype # => "socket"
File.delete('/tmp/link', '/tmp/pipe', '/tmp/socket') # Clean up.
Returns 'unknown' if the type cannot be determined.
# File 'file.c', line 7184
static VALUE
rb_stat_ftype(VALUE obj)
{
return rb_file_ftype(get_stat(obj)->ST_(mode));
}
#gid ⇒ Integer
# File 'file.c', line 910
static VALUE
rb_stat_gid(VALUE self)
{
return GIDT2NUM(get_stat(self)->ST_(gid));
}
#initialize_copy(orig)
# File 'file.c', line 7128
static VALUE
rb_stat_init_copy(VALUE copy, VALUE orig)
{
if (!OBJ_INIT_COPY(copy, orig)) return copy;
struct rb_stat *orig_rb_st;
TypedData_Get_Struct(orig, struct rb_stat, &stat_data_type, orig_rb_st);
struct rb_stat *copy_rb_st;
TypedData_Get_Struct(copy, struct rb_stat, &stat_data_type, copy_rb_st);
*copy_rb_st = *orig_rb_st;
return copy;
}
#ino ⇒ Integer
# File 'file.c', line 782
static VALUE
rb_stat_ino(VALUE self)
{
rb_io_stat_data *ptr = get_stat(self);
#ifdef HAVE_STRUCT_STAT_ST_INOHIGH
/* assume INTEGER_PACK_LSWORD_FIRST and st_inohigh is just next of st_ino */
return rb_integer_unpack(&ptr->st_ino, 2,
SIZEOF_STRUCT_STAT_ST_INO, 0,
INTEGER_PACK_LSWORD_FIRST|INTEGER_PACK_NATIVE_BYTE_ORDER|
INTEGER_PACK_2COMP);
#else
return UIANY2NUM(ptr->ST_(ino));
#endif
}
#inspect ⇒ String
Returns a string representation of the entry
in thesnapshot in self;
here we split the returned strings for readability:
puts File.stat('/etc/passwd').inspect.split(', ')
Output:
{#<}File::Stat dev=0x10302
ino=11141413
mode=0100644
nlink=1
uid=0
gid=0
rdev=0x0
size=3092
blksize=4096
blocks=8
atime=2026-10-07 11:25:01.307589569 -0500
mtime=2025-06-10 11:10:47.358999941 -0500
ctime=2025-06-10 11:10:47.38899994 -0500>
puts File.stat('/etc').inspect.split(', ')
Output:
{#<}File::Stat dev=0x10302
ino=11141121
mode=040755
nlink=143
uid=0
gid=0
rdev=0x0
size=12288
blksize=4096
blocks=24
atime=2026-10-04 14:29:46.907775905 -0500
mtime=2026-10-07 06:13:35.116915307 -0500
ctime=2026-10-07 06:13:35.116915307 -0500>
# File 'file.c', line 1482
static VALUE
rb_stat_inspect(VALUE self)
{
VALUE str;
size_t i;
static const struct {
const char *name;
VALUE (*func)(VALUE);
} member[] = {
{"dev", rb_stat_dev},
{"ino", rb_stat_ino},
{"mode", rb_stat_mode},
{"nlink", rb_stat_nlink},
{"uid", rb_stat_uid},
{"gid", rb_stat_gid},
{"rdev", rb_stat_rdev},
{"size", rb_stat_size},
{"blksize", rb_stat_blksize},
{"blocks", rb_stat_blocks},
{"atime", rb_stat_atime},
{"mtime", rb_stat_mtime},
{"ctime", rb_stat_ctime},
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC)
{"birthtime", rb_stat_birthtime},
#endif
};
struct rb_stat* rb_st;
TypedData_Get_Struct(self, struct rb_stat, &stat_data_type, rb_st);
if (!rb_st->initialized) {
return rb_sprintf("#<%s: uninitialized>", rb_obj_classname(self));
}
str = rb_str_buf_new2("#<");
rb_str_buf_cat2(str, rb_obj_classname(self));
rb_str_buf_cat2(str, " ");
for (i = 0; i < sizeof(member)/sizeof(member[0]); i++) {
VALUE v;
if (i > 0) {
rb_str_buf_cat2(str, ", ");
}
rb_str_buf_cat2(str, member[i].name);
rb_str_buf_cat2(str, "=");
v = (*member[i].func)(self);
if (i == 2) { /* mode */
rb_str_catf(str, "0%lo", (unsigned long)NUM2ULONG(v));
}
else if (i == 0 || i == 6) { /* dev/rdev */
rb_str_catf(str, "0x%"PRI_DEVT_PREFIX"x", NUM2DEVT(v));
}
else {
rb_str_append(str, rb_inspect(v));
}
}
rb_str_buf_cat2(str, ">");
return str;
}
#mode ⇒ Integer
# File 'file.c', line 816
static VALUE
rb_stat_mode(VALUE self)
{
return UINT2NUM(ST2UINT(get_stat(self)->ST_(mode)));
}
#mtime ⇒ Time
Returns a new ::Time object containing
themodification time
of the object in thesnapshot in self:
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo') # Create file.
File.mtime(filepath) # => 2026-10-13 10:48:10.477675435 -0500 # File mtime.
stat0 = File.stat(filepath) # Take snapshot.
stat0.mtime # => 2026-10-13 10:48:10.477675435 -0500 # Snapshot mtime.
File.write(filepath, 'bar') # Modify file.
File.mtime(filepath) # => 2026-10-13 10:48:37.584264035 -0500 # File mtime updated.
stat0.mtime # => 2026-10-13 10:48:10.477675435 -0500 # Snapshot mtime not updated.
stat1 = File.stat(filepath) # Take new snapshot.
stat1.mtime # => 2026-10-13 10:48:37.584264035 -0500 # New snapshot mtime.
File.delete(filepath) # Clean up.
# File 'file.c', line 1350
static VALUE
rb_stat_mtime(VALUE self)
{
return stat_time(statx_mtimespec(get_stat(self)));
}
#nlink ⇒ Integer
Returns the number ofhard links to the entry
in thesnapshot in self:
filepath = '/tmp/t.txt'
File.write(filepath, 'foo') # Creates hard link.
stat0 = File.stat(filepath) # Take snapshot.
stat0.nlink # => 1 # One hard link.
linkpath = '/tmp/t.bak'
File.link(filepath, linkpath) # Creates another hard link.
stat0.nlink # => 1 # Snapshot not updated.
stat1 = File.stat(filepath) # Take new snapshot.
stat1.nlink # => 2 # Updated nlink count.
File.unlink(linkpath) # Remove one hard link.
stat1.nlink # => 2 # Snapshot not updated.
stat2 = File.stat(filepath) # Take new shapshot.
stat2.nlink # => 1 # Updated nlink count.
File.unlink(filepath) # Clean up.
# File 'file.c', line 850
static VALUE
rb_stat_nlink(VALUE self)
{
/* struct stat::st_nlink is nlink_t in POSIX. Not the case for Windows. */
const rb_io_stat_data *ptr = get_stat(self);
return UIANY2NUM(ptr->ST_(nlink));
}
#rdev ⇒ Integer?
Returns the integer device type of the entry
in thesnapshot in self.
On a non-Windows system, returns a filesystem-dependent device type; the returned value is non-zero only for character-special and block-special devices.
In these hexadecimal-formatted examples:
- The two-low order digits contain the device-type minor value, which identifies a specific instance, port, or partition managed by a device driver; see #rdev_minor.
- The higher-order digits contain the device-type major value, which identifies the device driver; see #rdev_major.
#### Character-special devices.
'%08x' % File.stat('/dev/null').rdev # => "00000103"
'%08x' % File.stat('/dev/zero').rdev # => "00000105"
'%08x' % File.stat('/dev/random').rdev # => "00000108"
'%08x' % File.stat('/dev/tty').rdev # => "00000500"
'%08x' % File.stat('/dev/tty0').rdev # => "00000400"
'%08x' % File.stat('/dev/tty1').rdev # => "00000401"
#### Block-special devices.
'%08x' % File.stat('/dev/nvme0n1').rdev # => "00010300"
'%08x' % File.stat('/dev/nvme0n1p1').rdev # => "00010301"
'%08x' % File.stat('/dev/nvme0n1p2').rdev # => "00010302"
On Windows, returns a number corresponding to the given device:
File.stat('C:/').rdev # => 2
File.stat('C:/Users').rdev # => 2
File.stat('D:/').rdev # => 3
# File 'file.c', line 962
static VALUE
rb_stat_rdev(VALUE self)
{
#if RUBY_USE_STATX
unsigned int m = get_stat(self)->stx_rdev_major;
unsigned int n = get_stat(self)->stx_rdev_minor;
return ULL2NUM(makedev(m, n));
#elif !defined(HAVE_STRUCT_STAT_ST_RDEV)
return Qnil;
#elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_DEV_T
return DEVT2NUM(get_stat(self)->ST_(rdev));
#elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_LONG
return ULONG2NUM(get_stat(self)->ST_(rdev));
#else
return ULL2NUM(get_stat(self)->ST_(rdev));
#endif
}
#rdev_major ⇒ Integer
Returns the integer major portion of the device type of the entry
in thesnapshot in self;
see also #rdev, which returns the entire device type (major and minor).
On a non-Windows system, returns a filesystem-dependent major device type, which identifies the device driver; the returned value is non-zero only for character-special and block-special devices:
#### Character-special devices.
'%04x' % File.stat('/dev/null').rdev_major # => "0001"
'%04x' % File.stat('/dev/zero').rdev_major # => "0001"
'%04x' % File.stat('/dev/random').rdev_major # => "0001"
'%04x' % File.stat('/dev/tty').rdev_major # => "0005"
'%04x' % File.stat('/dev/tty0').rdev_major # => "0004"
'%04x' % File.stat('/dev/tty1').rdev_major # => "0004"
#### Block-special devices.
'%04x' % File.stat('/dev/nvme0n1').rdev_major # => "0103"
'%04x' % File.stat('/dev/nvme0n1p1').rdev_major # => "0103"
'%04x' % File.stat('/dev/nvme0n1p2').rdev_major # => "0103"
On Windows, returns nil.
# File 'file.c', line 1012
static VALUE
rb_stat_rdev_major(VALUE self)
{
#if RUBY_USE_STATX
return UINT2NUM(get_stat(self)->stx_rdev_major);
#elif defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(major)
return UINT2NUM(major(get_stat(self)->ST_(rdev)));
#else
return Qnil;
#endif
}
#rdev_minor ⇒ Integer
Returns the integer minor portion of the device type of the entry
in thesnapshot in self;
see also #rdev, which returns the entire device type (major and minor).
On a non-Windows system, returns a filesystem-dependent minor device type, which identifies a specific instance, port, or partition on its device; the returned value is non-zero only for character-special and block-special devices:
#### Character-special devices.
'%02x' % File.stat('/dev/null').rdev_minor # => "03"
'%02x' % File.stat('/dev/zero').rdev_minor # => "05"
'%02x' % File.stat('/dev/random').rdev_minor # => "08"
'%02x' % File.stat('/dev/tty').rdev_minor # => "00"
'%02x' % File.stat('/dev/tty0').rdev_minor # => "00"
'%02x' % File.stat('/dev/tty1').rdev_minor # => "01"
#### Block-special devices.
'%02x' % File.stat('/dev/nvme0n1').rdev_minor # => "00"
'%02x' % File.stat('/dev/nvme0n1p1').rdev_minor # => "01"
'%02x' % File.stat('/dev/nvme0n1p2').rdev_minor # => "02"
On Windows, returns nil.
# File 'file.c', line 1056
static VALUE
rb_stat_rdev_minor(VALUE self)
{
#if RUBY_USE_STATX
return UINT2NUM(get_stat(self)->stx_rdev_minor);
#elif defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(minor)
return UINT2NUM(minor(get_stat(self)->ST_(rdev)));
#else
return Qnil;
#endif
}
#uid ⇒ Integer
Returns the integer id of the owner of the entry
in thesnapshot in self:
Process.uid # => 0 # Superuser; can change owner.
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo') # Create file.
stat0 = File.stat(filepath) # Take snapshot.
stat0.uid # => 0 # Snapshot has uid.
File.chown(1000, 1000, filepath) # Change owner.
stat0.uid # => 0 # Snapshot not updated.
stat1 = File.stat(filepath) # Take new snapshot.
stat1.uid # => 1000 # Snapshot has new uid.
File.delete(filepath) # Clean up.
# File 'file.c', line 883
static VALUE
rb_stat_uid(VALUE self)
{
return UIDT2NUM(get_stat(self)->ST_(uid));
}