module FileTest

FileTest implements file test operations similar to those used in File::Stat. It exists as a standalone module, and its methods are also insinuated into the File class. (Note that this is not done by inclusion: the interpreter cheats).

Public Instance Methods

Returns whether object (a path or IO object) represents a block device (i.e., a direct-access device):

File.blockdev?('/dev/nvme0n1') # => true
File.blockdev?('/dev/loop0')   # => true
File.blockdev?('/dev/tty')     # => false
File.blockdev?('/dev/null')    # => false
File.blockdev?('nosuch')       # => false
File.blockdev?($stdin)         # => false

The returned value is filesystem-dependent; returns false on Windows or MacOS.

static VALUE
rb_file_blockdev_p(VALUE obj, VALUE fname)
{
#ifndef S_ISBLK
#   ifdef S_IFBLK
#       define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
#   else
#       define S_ISBLK(m) (0)  /* anytime false */
#   endif
#endif

#ifdef S_ISBLK
    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qfalse;
    if (S_ISBLK(st.st_mode)) return Qtrue;

#endif
    return Qfalse;
}

Returns whether object (a path or IO object) represents a character device (i.e., a sequential-access device):

File.chardev?('/dev/tty')     # => true
File.chardev?('/dev/null')    # => true
File.chardev?($stdin)         # => true
File.chardev?('/dev/nvme0n1') # => false
File.chardev?('/dev/loop0')   # => false
File.chardev?('nosuch')       # => false

The returned value is filesystem-dependent; on Windows, always false.

static VALUE
rb_file_chardev_p(VALUE obj, VALUE fname)
{
#ifndef S_ISCHR
#   define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
#endif

    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qfalse;
    if (S_ISCHR(st.st_mode)) return Qtrue;

    return Qfalse;
}

Returns whether the given object represents a directory; object may be a string path or an IO object:

File.directory?('/etc')        # => true
File.directory?('/etc/passwd') # => false
File.directory?($stdin)        # => false
File.directory?('nosuch')      # => false
VALUE
rb_file_directory_p(VALUE obj, VALUE fname)
{
#ifndef S_ISDIR
#   define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif

    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qfalse;
    if (S_ISDIR(st.st_mode)) return Qtrue;
    return Qfalse;
}

Returns whether the given object exists and has size zero.

The given object may be the path to a file:

filepath = '/tmp/t.tmp'
File.write(filepath, 'foo') # File has non-zero size.
File.zero?(filepath)        # => false
File.truncate(filepath, 0)  # File has zero size.
File.zero?(filepath)        # => true
File.delete(filepath)       # Clean up.

The given object may be the path to a directory:

dirpath = '/tmp/foo'
Dir.mkdir(dirpath)
Dir.new(dirpath).children.size # => 0
# Size is filesystem-dependent; may or may not be zero.
File.size(dirpath)             # => 4096
File.zero?(dirpath)            # => false
filepath = '/tmp/foo/t.tmp'    # => "/tmp/foo/t.tmp"
File.write(filepath, 'foo')    # Add a child.
Dir.new(dirpath).children.size # => 1
File.size(dirpath)             # => 4096
File.zero?(dirpath)            # => false
FileUtils.rm_rf(dirpath)       # Clean up.

The given object may be an IO object:

File.zero?($stdin) # => true

The given object may be none of the above:

File.zero?('nosuch') # => false
Alias for: zero?

Returns whether the filesystem entry at the given path is executable by the effective owner/group of the current process:

File.executable?('/bin/ruby')   # => true
File.executable?('/etc')        # => true
File.executable?('/etc/passwd') # => false
File.executable?('nosuch')      # => false

Note that some filesystem settings may cause this method to return true even though the entry is not executable by the effective owner/group.

static VALUE
rb_file_executable_p(VALUE obj, VALUE fname)
{
    return RBOOL(rb_eaccess(fname, X_OK) >= 0);
}

Like File.executable?, but checks against the real owner/group instead of the effective owner/group.

static VALUE
rb_file_executable_real_p(VALUE obj, VALUE fname)
{
    return RBOOL(rb_access(fname, X_OK) >= 0);
}

Return whether the specified object, a string path or IO object, exists:

# String paths.
File.exist?('README.md') # => true
File.exist?('.')         # => true
filepath = 't.tmp'
File.exist?(filepath)    # => false
File.write(filepath, 'foo')
File.exist?(filepath)    # => true
# File (IO object).
file = File.new(filepath)
File.exist?(file)        # => true
file.close               # Clean up.
File.unlink(filepath)    # Clean up.

Follows symbolic links:

# Symbolic links.
File.symlink('README.md', 'README.link')
File.symlink('nosuch', 'BROKEN.link')
File.exist?('README.link')  # => true
File.exist?('BROKEN.link')  # => false
File.unlink('README.link')  # Clean up.
File.unlink('BROKEN.link')  # Clean up.
static VALUE
rb_file_exist_p(VALUE obj, VALUE fname)
{
    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qfalse;
    return Qtrue;
}

Returns whether the given object, a string path or IO object, represents a filesystem entry that exists and is a regular file; see File.ftype:

File.file?('/etc/passwd') # => true
File.file?('/etc')        # => false
File.file?($stdin)        # => false
File.file?('nosuch')      # => false
static VALUE
rb_file_file_p(VALUE obj, VALUE fname)
{
    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qfalse;
    return RBOOL(S_ISREG(st.st_mode));
}

Returns false on Windows.

On other systems returns whether the effective group id of the calling process is the owner of the given object.

The given object may be the string path or an IO object:

filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
File.grpowned?(filepath)       # => true
File.grpowned?('/etc')         # => false
File.grpowned?('/etc/passwd')  # => false
File.grpowned?($stdin)         # => false
File.grpowned?('nosuch')       # => false
File.delete(filepath)          # Clean up.
static VALUE
rb_file_grpowned_p(VALUE obj, VALUE fname)
{
#ifndef _WIN32
    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qfalse;
    if (rb_group_member(st.st_gid)) return Qtrue;
#endif
    return Qfalse;
}

Returns whether the given objects represent filesystem entries that are identical; each object may be a string path or an IO object:

# Paths.
File.identical?('README.md', 'README.md')   # => true  # Same path.
File.identical?('README.md', './README.md') # => true  # Same entry.
File.identical?('.', '.')                   # => true  # Directory.
File.identical?('README.md', 'LEGAL')       # => false
File.identical?('README.md', 'nosuch')      # => false # Non-existent entry.
# Links and File object.
File.link('README.md', 'link')              # Symbolic link.
File.symlink('README.md', 'symlink')        # Hard link.
file = File.open('README.md', 'r')          # File object.
File.identical?('README.md', 'link')        # => true
File.identical?('README.md', 'symlink')     # => true
File.identical?('README.md', file)          # => true
# Clean up.
File.unlink('link')
File.unlink('symlink')
file.close
static VALUE
rb_file_identical_p(VALUE obj, VALUE fname1, VALUE fname2)
{
#ifndef _WIN32
    struct stat st1, st2;

    if (rb_stat(fname1, &st1) < 0) return Qfalse;
    if (rb_stat(fname2, &st2) < 0) return Qfalse;
    if (st1.st_dev != st2.st_dev) return Qfalse;
    if (st1.st_ino != st2.st_ino) return Qfalse;
    return Qtrue;
#else
    extern VALUE rb_w32_file_identical_p(VALUE, VALUE);
    return rb_w32_file_identical_p(fname1, fname2);
#endif
}

Returns whether the given object represents a filesystem entry or IO object that exists and is owned by the user of the current process:

filepath = 'doc/t.tmp'
File.write(filepath, 'foo')
File.owned?(filepath) # => true
File.delete(filepath) # Clean up.
dirpath = 'doc/tmp'
Dir.mkdir(dirpath)
File.owned?(dirpath)  # => true
Dir.rmdir(dirpath)    # Clean up.
File.owned?($stdin)   # => true
File.owned?('/etc')   # => false
static VALUE
rb_file_owned_p(VALUE obj, VALUE fname)
{
    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qfalse;
    return RBOOL(st.st_uid == geteuid());
}

Returns whether the entry at the given path is a pipe:

File.pipe?('doc/syntax/')        # => false  # Directory.
File.pipe?('doc/maintainers.md') # => false  # Regular file.
File.pipe?('nosuch')             # => false  # Non-existent.
path = '/tmp/foo'
File.mkfifo(path)
File.pipe?(path)                 # => true
File.delete(path)                # Clean up.
static VALUE
rb_file_pipe_p(VALUE obj, VALUE fname)
{
#ifdef S_IFIFO
#  ifndef S_ISFIFO
#    define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
#  endif

    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qfalse;
    if (S_ISFIFO(st.st_mode)) return Qtrue;

#endif
    return Qfalse;
}

Returns whether the entry at the given path exists and is readable by the owner and group of the current process; see Permissions:

path = '/tmp/secret.txt'
File.write(path, 'foo')
File.readable?(path)     # => true
File.chmod(0o000, path)
File.readable?(path)     # => false
File.delete(path)        # Clean up.
File.readable?('nosuch') # => false
static VALUE
rb_file_readable_p(VALUE obj, VALUE fname)
{
    return RBOOL(rb_eaccess(fname, R_OK) >= 0);
}

Like File.readable?, but checks against the real user and group ids instead of the effective ids.

static VALUE
rb_file_readable_real_p(VALUE obj, VALUE fname)
{
    return RBOOL(rb_access(fname, R_OK) >= 0);
}

Returns whether the setgid bit is set in the special bits for the given object, which may be a path or an IO object:

path = '/tmp/t.tmp'
File.write(path, 'foo')
mode = File.stat(path).mode.to_s(8) # => "100664"
File.setgid?(path)                  # => false
File.chmod(0o2644, path)            # Set the bit.
mode = File.stat(path).mode.to_s(8) # => "102644"
File.setgid?(path)                  # => true
File.delete(path)                   # Clean up.
File.setgid?($stdin)                # => false

On Windows, the bit is never set; the method always returns false.

static VALUE
rb_file_sgid_p(VALUE obj, VALUE fname)
{
#ifdef S_ISGID
    return check3rdbyte(fname, S_ISGID);
#else
    return Qfalse;
#endif
}

Returns whether the setuid bit is set in the special bits for the given object, which may be a path or an IO object:

path = '/tmp/t.tmp'
File.write(path, 'foo')
mode = File.stat(path).mode.to_s(8) # => "100664"
File.setuid?(path)                  # => false
File.chmod(0o4644, path)            # Set the bit.
mode = File.stat(path).mode.to_s(8) # => "104644"
File.setuid?(path)                  # => true
File.delete(path)                   # Clean up.
File.setuid?($stdin)                # => false

On Windows, the bit is never set; the method always returns false.

static VALUE
rb_file_suid_p(VALUE obj, VALUE fname)
{
#ifdef S_ISUID
    return check3rdbyte(fname, S_ISUID);
#else
    return Qfalse;
#endif
}

Returns the size in bytes of the given object, which may be a path or an IO object:

File.size('doc/maintainers.md') # => 14900  # Regular file.
File.size('doc/syntax/')        # => 4096   # Directory.
File.size($stdin)               # => 0      # IO object.
static VALUE
rb_file_s_size(VALUE klass, VALUE fname)
{
    struct stat st;

    if (rb_stat(fname, &st) < 0) {
        int e = errno;
        FilePathValue(fname);
        rb_syserr_fail_path(e, fname);
    }
    return OFFT2NUM(st.st_size);
}

Returns the size in bytes of the given object if the entry exists and has non-zero size, nil otherwise; the object may be a path or an IO object:

# Regular file.
path = '/tmp/t.tmp'
File.write(path, 'foo')
File.size?(path)   # => 3    # Non-zero size.
File.write(path, '')
File.size?(path)   # => nil  # Zero size.
File.delete(path)            # Clean up.
File.size?(path)   # => nil  # Non-existent.
# Directory.
path = '/tmp/foo/'
Dir.mkdir(path)
File.size?(path)   # => 4096 # Non-zero size.
Dir.rmdir(path)              # Clean up.
File.size?(path)   # => nil  # Non-existent.
static VALUE
rb_file_size_p(VALUE obj, VALUE fname)
{
    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qnil;
    if (st.st_size == 0) return Qnil;
    return OFFT2NUM(st.st_size);
}

Returns whether the given object represents a socket; the object may be a path or an IO object:

require 'socket'
sock_path = '/tmp/socket'
server = UNIXServer.new(sock_path)
File.socket?(sock_path) # => true
File.delete(sock_path)  # Clean up.
file_path = '/etc/passwd'
File.file?(file_path)   # => true
File.socket?(file_path) # => false
File.socket?($stdin)    # => false
File.socket?('nosuch')  # => false
static VALUE
rb_file_socket_p(VALUE obj, VALUE fname)
{
#ifndef S_ISSOCK
#  ifdef _S_ISSOCK
#    define S_ISSOCK(m) _S_ISSOCK(m)
#  else
#    ifdef _S_IFSOCK
#      define S_ISSOCK(m) (((m) & S_IFMT) == _S_IFSOCK)
#    else
#      ifdef S_IFSOCK
#        define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
#      endif
#    endif
#  endif
#endif

#ifdef S_ISSOCK
    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qfalse;
    if (S_ISSOCK(st.st_mode)) return Qtrue;
#endif

    return Qfalse;
}

Returns whether the sticky bit is set in the special bits for the given object, which may be a path or an IO object:

filepath = '/tmp/t.tmp'
File.write(filepath, 'foo')
mode = File.stat(filepath).mode.to_s(8) # => "100664"
File.sticky?(filepath)                  # => false
File.chmod(01644, filepath)             # Set sticky bit.
mode = File.stat(filepath).mode.to_s(8) # => "101644"
File.sticky?(filepath)                  # => true
File.delete(filepath)                   # Clean up.
File.sticky?($stdin)                    # => false
File.sticky?('nosuch')                  # => false

Returns false on Windows.

static VALUE
rb_file_sticky_p(VALUE obj, VALUE fname)
{
#ifdef S_ISVTX
    return check3rdbyte(fname, S_ISVTX);
#else
    return Qfalse;
#endif
}

If the the given object exists and is readable by others, returns the integer permissions for the entry; otherwise, returns nil:

filepath = '/tmp/t.tmp'
File.world_readable?(filepath)         # => nil   # Non-existent.
File.write(filepath, 'foo')                       # Create file.
File.world_readable?(filepath).to_s(8) # => "664" # World-readable.
File.chmod(0o000, filepath)                       # Change to unreadable.
File.world_readable?(filepath)         # => nil   # Not world-readable.
File.delete(filepath)                             # Clean up.
File.world_readable?('.').to_s(8)      # => "775" # Directory.
File.world_readable?($stdin)           # => nil   # IO object.
static VALUE
rb_file_world_readable_p(VALUE obj, VALUE fname)
{
#ifdef S_IROTH
    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qnil;
    if ((st.st_mode & (S_IROTH)) == S_IROTH) {
        return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO));
    }
#endif
    return Qnil;
}

If the given object exists and is writable by others, returns the integer permissions for the entry; otherwise, returns nil:

filepath = '/tmp/t.tmp'
File.world_writable?(filepath)         # => nil    # Non-existent.
File.write(filepath, 'foo')                        # Create file.
File.world_writable?(filepath)         # => nil    # Not world-writable.
File.chmod(0o777, filepath)                        # Make world-writable.
File.world_writable?(filepath).to_s(8) # => "777"  # World-writable.
File.delete(filepath)                              # Clean up.
File.world_writable?('/tmp').to_s(8)   # => "777"  # Directory.
File.world_writable?($stdin)           # => nil    # IO object.
static VALUE
rb_file_world_writable_p(VALUE obj, VALUE fname)
{
#ifdef S_IWOTH
    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qnil;
    if ((st.st_mode & (S_IWOTH)) == S_IWOTH) {
        return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO));
    }
#endif
    return Qnil;
}

Returns whether given object exists and is writable by the owner and group in the current process:

filepath = '/tmp/secret.txt'
File.writable?(filepath)   # => false  # Non-existent.
File.write(filepath, 'foo')            # Create file.
File.writable?(filepath)   # => true   # Writable.
File.chmod(0o000, filepath)            # Make non-writable.
File.writable?(filepath)   # => false  # Not writable.
File.delete(filepath)                  # Clean up.
File.writable?('/etc')     # => false  # Directory.
File.writable?($stdin)     # => false  # IO object.

Note that filesystem security features may cause this method to return true even when the file is not writable by the owner and group.

static VALUE
rb_file_writable_p(VALUE obj, VALUE fname)
{
    return RBOOL(rb_eaccess(fname, W_OK) >= 0);
}

Like File.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 object is not writable by the real owner and group.

static VALUE
rb_file_writable_real_p(VALUE obj, VALUE fname)
{
    return RBOOL(rb_access(fname, W_OK) >= 0);
}

Returns whether the given object exists and has size zero.

The given object may be the path to a file:

filepath = '/tmp/t.tmp'
File.write(filepath, 'foo') # File has non-zero size.
File.zero?(filepath)        # => false
File.truncate(filepath, 0)  # File has zero size.
File.zero?(filepath)        # => true
File.delete(filepath)       # Clean up.

The given object may be the path to a directory:

dirpath = '/tmp/foo'
Dir.mkdir(dirpath)
Dir.new(dirpath).children.size # => 0
# Size is filesystem-dependent; may or may not be zero.
File.size(dirpath)             # => 4096
File.zero?(dirpath)            # => false
filepath = '/tmp/foo/t.tmp'    # => "/tmp/foo/t.tmp"
File.write(filepath, 'foo')    # Add a child.
Dir.new(dirpath).children.size # => 1
File.size(dirpath)             # => 4096
File.zero?(dirpath)            # => false
FileUtils.rm_rf(dirpath)       # Clean up.

The given object may be an IO object:

File.zero?($stdin) # => true

The given object may be none of the above:

File.zero?('nosuch') # => false
static VALUE
rb_file_zero_p(VALUE obj, VALUE fname)
{
    struct stat st;

    if (rb_stat(fname, &st) < 0) return Qfalse;
    return RBOOL(st.st_size == 0);
}
Also aliased as: empty?