++ed by:

3 PAUSE users
2 non-PAUSE users.

and 1 contributors




version 0.0405


  use Evo '-Fs; File::Basename fileparse';
  my $fs = Evo::Fs->new(root => '/tmp/testfs');

  say "/foo => ", $fs->path2real('/foo');
  say "foo => ",  $fs->path2real('/foo');    # the same

  my $fh = $fs->open('foo/bar.txt', 'w');    # open and create '/foo' if necessary

  $fs->write('a/foo', 'one');                # /tmp/test/a/foo
  $fs->append('/a/foo', 'two');              # /tmp/test/a/foo
  say $fs->read('a/foo');                    # onetwo
  say $fs->read('/a/foo');                   # the same

  # bulk
  $fs->write_many('/a/foo' => 'afoo', '/b/foo' => 'bfoo');

  # copying
  $fs->write('/from/d/f' => 'OK');

  # copy file
  $fs->remove_tree('/to') if $fs->exists('/to');
  $fs->copy_file('/from/d/f' => '/to/d/f');
  say $fs->read('/to/d/f');    # OK

  # copy dir recursively
  $fs->remove_tree('/to') if $fs->exists('/to');
  $fs->copy_dir('/from' => '/to');
  say $fs->read('/to/d/f');    # OK

  $fs->sysopen($fh, '/c', 'w+');
  $fs->syswrite($fh, "123456");
  $fs->sysseek($fh, 0);
  $fs->sysread($fh, \my $buf, 3);
  say $buf;                                  # 123

  # traversing

    # where to start (/ => /tmp/testfs)

    # do something with file
    sub ($path) {
      say $path;

    # skip dirs like .git
    sub ($path) {
      scalar fileparse($path) !~ /^\./;

    sub ($path) {
      say "FOUND: ", $path;

  use Evo::Fs 'FSROOT';
  say join ', ', FSROOT->ls('/');


An abstraction url-like layer between file system and your application. Every path is relative to root.

You wan't be able to do something like this:

  my $fs = Evo::Fs->new(root => '/tmp/fs');
  my $path = '../fs2/foo';

This is a security protection. But you can use "cd" instead

  my $fs2 = $fs->cd('../fs2');
  $fs2->write('foo' => 'OK');



Return a single instance of Evo::Fs where root is /



  my $fs = Evo::Fs->new(root => '/tmp/test-root');



Create a new Evo::FS instance. Also this is the only way to traverse up

  my $fs       = Evo::Fs->new(root => '/tmp/fs');
  my $fs2      = $fs->cd('../fs2');
  my $fs_child = $fs->cd('child');

copy_file($self, $from, $to)

Copy file, die if already exists

copy_dir($self, $from, $to)

Copy directory recursively, if directory $toexists, replace it content, create it otherwise. And for the children do the same

This functions kinda try to synchronize one path with another. Unlike cp -a, 2 invocations of this functions will lead to the same result (cp tries to check, if directory $to exists and copies $from to it in this case, this functions won't do this)

  my $fs = Evo::Fs->new(root => File::Temp->newdir);
  $fs->write('/base/child/file' => 'OK');
  $fs->make_tree('/copy/child'); # just to show that directory can exist
  $fs->copy_dir('/base', 'copy');
  say $fs->read('/copy/child/file'); # OK

In this example, directory /copy/child already exists, so a single file /base/child/file will be silenty copied to /copy/child/file

sysopen ($self, $path, $mode, $perm=...)

  my $fh = $fs->open('/foo/bar.txt', 'w');

Open a file and return a filehandle. Create parent directories if necessary. See "sysopen" for list of modes

append, write, read, read_ref

  $fs->write('/tmp/my/file', 'foo');
  $fs->append('/tmp/my/file', 'bar');
  say $fs->read('/tmp/my/file');            # foobar
  say $fs->read_ref('/tmp/my/file')->$*;    # foobar

Read, write or append a content to the file. Dirs will be created if they don't exist. Use lock 'ex' for append and write and lock 'sh' for read during each invocation


Write many files using write

sysseek($self, $position, $whence='start')

Whence can be one of:

sysread ($self, $fh, $ref, $length[, $offset])

Call sysread but accepts scalar reference for convinience

syswrite($self, $fh, $scalar, $length, $offset)

Call syswrite

sysopen ($self, $fh, $path, $mode, $perm=...)

  $fs->sysopen(my $fh, '/tmp/foo', 'r');

Mode can be one of:

* w Open file for writing. The file is created (if it does not exist) or truncated (if it exists). * wx Like w but fails if path exists. * w+ Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists). * wx+ Like w+ but fails if path exists.

* a Open file for appending. The file is created if it does not exist. * ax Like a but fails if path exists. * a+ Open file for reading and appending. The file is created if it does not exist. * ax+ Like a+ but fails if path exists.

rename($self, $oldpath, $newpath)

Rename a file.

stat($self, $path)

Return a Evo::Fs::Stat object


Convert a virtual path to the real one.

find_files($self, $dirs, $fn, $pick=undef)

  $fs->find_files('./tmp', sub ($fh) {...}, sub ($dir) {...});
  $fs->find_files(['/tmp'], sub ($fh) {...});

Find files in given directories. You can skip some directories by providing $pick->($dir) function. This will work ok on circular links, hard links and so on. Every path will be passed to $fn->($fh)only once even if it has many links.

So, in situations, when a file have several hard and symbolic links, only one of them will be passed to $fn, and potentially each time it can be different path for each find_files invocation.

See "traverse" for examining all nodes. This method just decorate it's arguments


You can also traverse all files, but ignore hidden directories, like ".git" this way:

  use Evo '-Fs FS SKIP_HIDDEN';
  FS->find_files('./', sub($path) { say $path; }, SKIP_HIDDEN)

traverse($self, $dirs, $fn, $pick=undef)

Traverse directories and invoke $fn->$path for each child node.

Each file is processed only once no matter how many links it has. So instead of a real filename you may be getting a link and never a real name depending on which one (file or link) was met first

You can provide $pick->($dir) to skip directories, for example, to skip hidden ones. By default all directories are processed

  $fs->traverse('/tmp', sub ($path) {...}, sub ($dir) {...});
  $fs->traverse(['/tmp'], sub ($path) {...},);

Also this method doesn't try to access directories without X and R permissions or pass them to $pick (but such directories will be passed to fn because are regular nodes)

In most cases you may want to use "find_files" instead.




This software is copyright (c) 2016 by alexbyk.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.