20

How do I check if a directory exists in lua, preferably without using the LuaFileSystem module if possible?

Trying to do something like this python line:

os.path.isdir(path)
Dan
  • 33,953
  • 24
  • 61
  • 87

13 Answers13

28

This is a way that works on both Unix and Windows, without any external dependencies:

--- Check if a file or directory exists in this path
function exists(file)
   local ok, err, code = os.rename(file, file)
   if not ok then
      if code == 13 then
         -- Permission denied, but it exists
         return true
      end
   end
   return ok, err
end

--- Check if a directory exists in this path
function isdir(path)
   -- "/" works on both Unix and Windows
   return exists(path.."/")
end
Hisham H M
  • 6,398
  • 1
  • 29
  • 30
  • 8
    Very clever using `os.rename` for that – Lucas Siqueira Dec 31 '17 at 16:38
  • Just FYI, the third return value is a Lua 5.2 feature. (See the [5.1](https://www.lua.org/manual/5.1/manual.html#pdf-os.rename) and [5.2](https://www.lua.org/manual/5.2/manual.html#pdf-os.rename) reference manuals.) It does seem to be supported in LuaJIT, though [not documented](https://luajit.org/extensions.html). – Elliott Slaughter Jun 13 '22 at 21:29
  • FYI, you will probably need to pass the absolute path to `os.rename` instead of the relative one to make your code stable. – NeoZoom.lua Dec 14 '22 at 22:32
16

The problem is that the stock Lua distribution (nearly) only includes features that are specified in standard C. Standard C makes no presumptions about there actually being a file system of any specific sort out there (or even an operating system, for that matter), so the os and io modules don't provide access information not available from the standard C library.

If you were attempting to code in pure standard C, you would have the same issue.

There is a chance that you can learn whether the folder exists implicitly from an attempt to use it. If you expect it to exist and be writable to you, then create a temporary file there and if the that succeeds, the folder exists. If it fails, you might not be able to distinguish a non-existent folder from insufficient permissions, of course.

By far the lightest-weight answer to getting a specific answer would be a thin binding to just those OS-specific function calls that provide the information you need. If you can accept the lua alien module, then you can like do the binding in otherwise pure Lua.

Simpler, but slightly heavier, is to accept Lua File System. It provides a portable module that supports most things one might want to learn about files and the file system.

RBerteig
  • 41,948
  • 7
  • 88
  • 128
9

If you're specifically interested in avoiding the LFS library, the Lua Posix library has an interface to stat().

require 'posix'
function isdir(fn)
    return (posix.stat(fn, "type") == 'directory')
end
Community
  • 1
  • 1
adrian
  • 1,447
  • 15
  • 24
6

Well, the 5.1 reference manual doesn't have anything in the os table, but if you use Nixstaller, you get os.fileexists for precisely what you've explained.

If you can afford to fiddle around a bit, or if you know what OS you'll be running on, you might get away with the standard os library's os.execute with some system call that will identify if the file exists.

Even better than os.execute might be os.rename:

os.rename(oldname, newname)

Renames file named oldname to newname. If this function fails, it returns nil, plus a string describing the error.

You could try setting oldname and newname the same -- you might not have write permissions, though, so it might fail because you can't write, even though you can read. In that event, you'd have to parse the returned error string and deduce whether you could write, or you'd have to just try executing your function that needs an existing file, and wrap it in a pcall.

Mark Rushakoff
  • 249,864
  • 45
  • 407
  • 398
4

You can also use the 'paths' package. Here's the link to the package

Then in Lua do:

require 'paths'

if paths.dirp('your_desired_directory') then
    print 'it exists'
else
    print 'it does not exist'
end
Amir
  • 10,600
  • 9
  • 48
  • 75
3

This is tested for the windows platform. It is quite easy actually:

local function directory_exists( sPath )
  if type( sPath ) ~= "string" then return false end

  local response = os.execute( "cd " .. sPath )
  if response == 0 then
    return true
  end
  return false
end

Obviously, this may not work on other OS's. But for windows users, this can be a solution :)

Engineer
  • 39
  • 1
  • When I tested on linux and windows, `response` is `nil` regardless of the existance of `sPath`? – Alex Nov 09 '17 at 22:14
  • 1
    It's usually not considered good practice to call an external program for a task as simple as this, because that tends to create as many problems as it solves. On Unix-like OSes the `sPath` argument should be quoted (not sure about Windows) and that's hard to do correctly. – Lassi Nov 27 '17 at 12:41
2

here is a simple way to check if a folder exists WITHOUT ANY EXTERNAL LIBRARY DEPENDENCIES :)

function directory_exists(path)
  local f  = io.popen("cd " .. path)
  local ff = f:read("*all")

  if (ff:find("ItemNotFoundException")) then
    return false
  else
    return true
  end  
end

print(directory_exists("C:\\Users"))
print(directory_exists("C:\\ThisFolder\\IsNotHere"))

If you copy and paste the above into Lua you should see

false
true

good luck :)

user1709076
  • 2,538
  • 9
  • 38
  • 59
  • 5
    This solution appears to assume that the user is using Powershell... Checking the return code from the cd command would probably work, though. – djs Jan 26 '13 at 19:48
  • I was expecting the results to be true for the first, false for the second. – RAL May 28 '13 at 16:56
  • @RAL just put `not directory_exists("C:\\ThisFolder\\IsNotHere")` – Blanket Fox Mar 30 '21 at 06:06
2

I use these (but i actually check for the error):

require("lfs")
-- no function checks for errors.
-- you should check for them

function isFile(name)
    if type(name)~="string" then return false end
    if not isDir(name) then
        return os.rename(name,name) and true or false
        -- note that the short evaluation is to
        -- return false instead of a possible nil
    end
    return false
end

function isFileOrDir(name)
    if type(name)~="string" then return false end
    return os.rename(name, name) and true or false
end

function isDir(name)
    if type(name)~="string" then return false end
    local cd = lfs.currentdir()
    local is = lfs.chdir(name) and true or false
    lfs.chdir(cd)
    return is
end

os.rename(name1, name2) will rename name1 to name2. Use the same name and nothing should change (except there is a badass error). If everything worked out good it returns true, else it returns nil and the errormessage. You said you dont want to use lfs. If you dont you cant differentiate between files and directorys without trying to open the file (which is a bit slow but ok).

So without LuaFileSystem

-- no require("lfs")

function exists(name)
    if type(name)~="string" then return false end
    return os.rename(name,name) and true or false
end

function isFile(name)
    if type(name)~="string" then return false end
    if not exists(name) then return false end
    local f = io.open(name)
    if f then
        f:close()
        return true
    end
    return false
end

function isDir(name)
    return (exists(name) and not isFile(name))
end

It looks shorter, but takes longer... Also open a file is a it risky, because of that you should use lfs. If you don't care about performance (and errorhandling -.-) you can just use it.

Have fun coding!

tDwtp
  • 480
  • 5
  • 12
  • 3
    `io.open` also works with directories. You would need trying to read it (`f:read(1)`) additionally. – blueyed Apr 05 '15 at 12:52
  • Thanks! Did not know that! Wouldn't that also throw an error if the file is empty (if I add 'f:read(1)')? And AFAIK 'f:read(0)' wouldn't help too... – tDwtp Jul 23 '15 at 02:46
1

This feels so simple that I feel like there's a reason nobody else has done this.

But it works on my machine (Ubuntu focal), for files and directories.

It does however say that existent-yet-forbidden files don't exist.

For quick scripts and/or few files:

function exists(path)
    return (io.open(path,"r") ~= nil)
end

Good practice:

function exists(path)
    local file = io.open(path,"r")
    if (file ~= nil) then
        io.close(file)
        return true
    else
        return false
    end
end
0

For Linux users:

function dir_exists( path )
if type( path ) ~= 'string' then
    error('input error')
    return false
end
local response = os.execute( 'cd ' .. path )
if response == nil then
    return false
end
return response
end
Franken
  • 1
  • 1
0

My preferred way of doing this in linux is

if os.execute '[ -e "/home" ]' then
  io.write "it exists"
  if os.execute '[ -d "/home" ]' then
    io.write " and is a directory"
  end
  io.write "\n"
end

or, to put this into a function:

function is_dir(path)
  return os.execute(('[ -d "%s" ]'):format(path))
end -- note that this implementation will return some more values
DarkWiiPlayer
  • 6,871
  • 3
  • 23
  • 38
0

nixio.fs

a package widely used in openwrt,
has lots of useful functions, and easy to use

docs: http://openwrt.github.io/luci/api/modules/nixio.fs.html#nixio.fs.stat

fs.stat, lstat

> fs = require'nixio.fs'
> st = fs.stat('/tmp')
> = st.type=='dir'
true

> = fs.lstat'/var'.type=='lnk'
true

> = fs.stat'/bin/sh'.type=='reg'
true

tests:

> = pj(fs.stat'/var')
{
    "dev": 17,
    "type": "dir",
    "modedec": 755,
    "rdev": 0,
    "nlink": 28,
    "atime": 1617348683,
    "blocks": 0,
    "modestr": "rwxr-xr-x",
    "ino": 1136,
    "mtime": 1666474113,
    "gid": 0,
    "blksize": 4096,
    "ctime": 1666474113,
    "uid": 0,
    "size": 1960
}
> = pj(fs.lstat'/var')
{
    "dev": 19,
    "type": "lnk",
    "modedec": 777,
    "rdev": 0,
    "nlink": 1,
    "atime": 1613402557,
    "blocks": 0,
    "modestr": "rwxrwxrwx",
    "ino": 1003,
    "mtime": 1613402557,
    "gid": 0,
    "blksize": 1024,
    "ctime": 1613402557,
    "uid": 0,
    "size": 3
}

about stat(), lstat() syscall

shell test operator
[ -e path ]
[ -f path ]
[ -d path ]
[ -L path ]

low level both rely on this syscall.

$ strace test -e /var |& grep stat | tail -1
stat("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0

$ strace test -f /var |& grep stat | tail -1
stat("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0

$ strace test -d /var |& grep stat | tail -1
stat("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0

$ strace test -L /var |& grep stat | tail -1
lstat("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
yurenchen
  • 1,897
  • 19
  • 17
-1
local function directory_exist(dir_path)
  local f = io.popen('[ -d "' .. dir_path .. '" ] && echo -n y')
  local result = f:read(1)
  f:close()
  return result == "y"
end

try this

superwf
  • 387
  • 2
  • 7