123456789_123456789_123456789_123456789_123456789_

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

Instance Attribute Summary

Instance Method Summary

::Comparable - Included

#<

Returns whether self is "less than" other; equivalent to (self <=> other) < 0:

#<=

Returns whether self is "less than or equal to" other; equivalent to (self <=> other) <= 0:

#==

Compares two objects based on the receiver's #<=> method, returning true if it returns 0.

#>

Returns whether self is "greater than" other; equivalent to (self <=> other) > 0:

#>=

Returns whether self is "greater than or equal to" other; equivalent to (self <=> other) >= 0:

#between?

Returns false if obj #<=> min is less than zero or if obj #<=> max is greater than zero, true otherwise.

#clamp

In (min, max) form, returns min if obj #<=> min is less than zero, max if obj #<=> max is greater than zero, and obj otherwise.

Constructor Details

.new(path) ⇒ Stat

Returns a new File::Stat object containing asnapshot of the filesystem entry at the given File.path:

File::Stat.new('/etc/passwd')
File::Stat.new('/tmp')
File::Stat.new('nosuch') # Raises Errno::ENOENT: No such file or directory.
[ GitHub ]

  
# File 'file.c', line 6994

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.

[ GitHub ]

  
# File 'file.c', line 7218

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.

[ GitHub ]

  
# File 'file.c', line 7247

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
[ GitHub ]

  
# File 'file.c', line 7093

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.

[ GitHub ]

  
# File 'file.c', line 7575

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.

[ GitHub ]

  
# File 'file.c', line 7608

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)

Returns whether self represents a filesystem entry that exists and is a regular file; see File.ftype:

File.stat('/etc/passwd').file? # => true
File.stat('/etc').file?        # => false
[ GitHub ]

  
# File 'file.c', line 7648

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.
[ GitHub ]

  
# File 'file.c', line 7320

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 self represents a filesystem entry that, at the time self was created, existed and was owned by the user of the current process; seeSnapshot:

filepath = 'doc/t.tmp'
File.write(filepath, 'foo')
filestat = File.stat(filepath)
filestat.owned?          # => true
File.delete(filepath)
filestat.owned?          # => true  # Snapshot unchanged.
dirpath = 'doc/tmp'
Dir.mkdir(dirpath)
dirstat = File.stat(dirpath)
dirstat.owned?           # => true
Dir.rmdir(dirpath)
dirstat.owned?           # => true  # Snapshot unchanged.
File.stat('/etc').owned? # => false
[ GitHub ]

  
# File 'file.c', line 7284

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:

File.stat('doc/syntax/').pipe?        # => false  # Directory .
File.stat('doc/maintainers.md').pipe? # => false  # Regular file.
path = '/tmp/foo'
File.mkfifo(path)
File.stat(path).pipe?                 # => true
File.delete(path)                     # Clean up.
[ GitHub ]

  
# File 'file.c', line 7119

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.
[ GitHub ]

  
# File 'file.c', line 7350

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 user and group ids instead of the effective ids.

[ GitHub ]

  
# File 'file.c', line 7382

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.

[ GitHub ]

  
# File 'file.c', line 7799

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.

[ GitHub ]

  
# File 'file.c', line 7762

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 of self in bytes:

File.stat('doc/maintainers.md').size # => 14900  # Regular file.
File.stat('doc/syntax/').size        # => 4096   # Directory.
#### When the file size changes.
path = '/tmp/t.tmp'
file = File.new(path, 'w+')
file.write('foo')
stat = File.stat(path) # Take snapshot.
stat.size              # => 3
file.write('bar')      # Change file size.
file.size              # => 6
stat.size              # => 3  # Snapshot unchanged.
stat = File.stat(path) # Fresh snapshot.
stat.size              # => 6  # Shapshot different.
#### Clean up.
file.close
File.delete(path)
[ GitHub ]

  
# File 'file.c', line 989

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:

path = '/tmp/t.tmp'
File.write(path, 'foo')
File.size(path)        # => 3
stat = File.stat(path)           # Take snapshot.
stat.size?             # => 3    # Non-zero size.
File.write(path, '')
File.size(path)        # => 0
stat.size?             # => 3    # Snapshot unchanged.
stat = File.stat(path)           # Take new snapshot.
stat.size?             # => nil  # Zero size
File.delete(path)                # Clean up.
[ GitHub ]

  
# File 'file.c', line 7725

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 self is a socket:

sock_path = '/tmp/socket'
server = UNIXServer.new(sock_path)
stat = File.stat(sock_path)
stat.socket?           # => true
File.delete(sock_path) # Clean up.
stat.socket?           # => true
file_path = '/etc/passwd'
File.exist?(file_path) # => true  # Snapshot not updated.
stat = File.stat(file_path)
stat.socket?           # => false
[ GitHub ]

  
# File 'file.c', line 7188

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.

[ GitHub ]

  
# File 'file.c', line 7836

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
[ GitHub ]

  
# File 'file.c', line 7156

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)

If the entry in self exists and is readable by others, returns the integerpermissions for the entry; otherwise, returns nil:

filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
File.stat(filepath).world_readable?.to_s(8) # => "664"  # World-readable.
File.chmod(0o000, filepath)                             # Make unreadable.
File.stat(filepath).world_readable?         # => nil    # Not world-readable.
File.delete(filepath)                                   # Clean up.
File.stat('.').world_readable?.to_s(8)      # => "775"  # Directory.
[ GitHub ]

  
# File 'file.c', line 7426

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.
[ GitHub ]

  
# File 'file.c', line 7541

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.

[ GitHub ]

  
# File 'file.c', line 7461

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.

[ GitHub ]

  
# File 'file.c', line 7496

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.
[ GitHub ]

  
# File 'file.c', line 7693

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, if self.mtime is earlier.
  • 0, if the two values are equal.
  • 1, if self.mtime is later.
  • nil, if other is 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.

[ GitHub ]

  
# 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.
[ GitHub ]

  
# File 'file.c', line 1210

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.
[ GitHub ]

  
# File 'file.c', line 1310

static VALUE
rb_stat_birthtime(VALUE self)
{
    return statx_birthtime(get_stat(self));
}

#blksize ⇒ Integer?

Returns the block size of the filesystem of thesnapshot in self, or nil on Windows:

File.stat('/etc').blksize        # => 4096
File.stat('/etc/passwd').blksize # => 4096
File.stat('nosuch').blksize      # Raises Errno::ENOENT: No such file or directory.
[ GitHub ]

  
# File 'file.c', line 1013

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?

Returns the number of filesystem blocks allocated for the entry in thesnapshot in self:

File.stat('/etc').blocks        # => 24
File.stat('/etc/passwd').blocks # => 8

Returns nil on Windows.

[ GitHub ]

  
# File 'file.c', line 1040

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.

See File System Timestamps.

[ GitHub ]

  
# File 'file.c', line 1282

static VALUE
rb_stat_ctime(VALUE self)
{
    return stat_time(statx_ctimespec(get_stat(self)));
}

#dev ⇒ Integer

Returns the device number for thesnapshot in self:

File.stat('/etc').dev # => 66306
File.stat('/dev').dev # => 5
[ GitHub ]

  
# 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

Returns nil on Windows.

On other systems, returns the major part of the device number for thesnapshot in self:

File.stat('/etc').dev_major # => 259
File.stat('/dev').dev_major # => 0
[ GitHub ]

  
# 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

Returns nil on Windows.

On other systems, returns the minor part of the device number for thesnapshot in self:

File.stat('/etc').dev_minor # => 2
File.stat('/dev').dev_minor # => 5
[ GitHub ]

  
# 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.

[ GitHub ]

  
# File 'file.c', line 7071

static VALUE
rb_stat_ftype(VALUE obj)
{
    return rb_file_ftype(get_stat(obj)->ST_(mode));
}

#gid ⇒ Integer

On Windows, returns 0.

On other systems, returns the group identifier of the owner of the entry in thesnapshot in self:

File.stat('/etc').gid   # => 0
filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
File.stat(filepath).gid # => 1000
File.delete(filepath)   # Clean up.
[ GitHub ]

  
# File 'file.c', line 880

static VALUE
rb_stat_gid(VALUE self)
{
    return GIDT2NUM(get_stat(self)->ST_(gid));
}

#initialize_copy(orig)

This method is for internal use only.
[ GitHub ]

  
# File 'file.c', line 7015

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

Returns the inode value for the entry in thesnapshot in self:

File.stat('/etc').ino        # => 11141121
File.stat('/etc/passwd').ino # => 11141413
[ GitHub ]

  
# 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>
[ GitHub ]

  
# File 'file.c', line 1375

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

Returns an integer representing the permissions of the entry in thesnapshot in self; seeFilesystem Modes.

The integer is most usefully formatted as six octal digits:

'%06o' % File.stat('/etc').mode        # => "040755"
'%06o' % File.stat('/etc/passwd').mode # => "100644"
[ GitHub ]

  
# 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 the modification time of the object represented by self at the time self was created; seeSnapshot:

path = 't.tmp'
file = File.new(path, 'w+')
stat = File.stat(path)
stat.mtime        # => 2026-09-19 08:49:08.846933858 -0500
file.write('foo')
file.flush
File.mtime(path)  # => 2026-09-19 08:50:14.381365572 -0500
stat.mtime        # => 2026-09-19 08:49:08.846933858 -0500
stat = File.stat(path)
stat.mtime        # => 2026-09-19 08:50:14.381365572 -0500
File.unlink(path) # Clean up.
[ GitHub ]

  
# File 'file.c', line 1243

static VALUE
rb_stat_mtime(VALUE self)
{
    return stat_time(statx_mtimespec(get_stat(self)));
}

#rdev ⇒ Integer?

Returns an integer representing the device type on which stat resides. Returns nil if the operating system doesn't support this feature.

File.stat("/dev/fd1").rdev   #=> 513
File.stat("/dev/tty").rdev   #=> 1280
[ GitHub ]

  
# File 'file.c', line 898

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 major part of #rdev or nil.

File.stat("/dev/fd1").rdev_major   #=> 2
File.stat("/dev/tty").rdev_major   #=> 5
[ GitHub ]

  
# File 'file.c', line 926

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 minor part of #rdev or nil.

File.stat("/dev/fd1").rdev_minor   #=> 1
File.stat("/dev/tty").rdev_minor   #=> 0
[ GitHub ]

  
# File 'file.c', line 948

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 numeric user id of the owner of stat.

File.stat("testfile").uid   #=> 501
[ GitHub ]

  
# File 'file.c', line 853

static VALUE
rb_stat_uid(VALUE self)
{
    return UIDT2NUM(get_stat(self)->ST_(uid));
}