Skip to content

Files and directories

Operations are recursive by default.

1
2
3
4
5
6
rm("a")
touch("foo")
val entries = ls("/")
symLink("a/b", "link")
hardLink("a/b", "link")
if (exists("foo")) { /* do something */ }

Moving around and temp dirs

  • Changing to a non-existent directory will create it.
  • cd can take a code block which changes the working dir only for the duration of that block.
  • mktmp returns a temp directory that will be recursively deleted when the script terminates.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cd(mktmp()) 
echo(dir)

// Creates subdir/another-subdir
cd("subdir/another-subdir") {
    echo(dir)
    write("file.txt", "Thoughtful statement.")
    echo(ls())
}

echo(dir)

Example of using cd and mktmp

Copying and moving files

1
2
cp("a", "b")
mv("a", "b")

cp and mv have more useful behavior than the UNIX cp command.

cp

  • Overlays. If source is a directory and dest is a pre-existing directory, the contents of source are recursively copied into dest, replacing whatever was there. That is, source/foo is copied to destination/foo not destination/source/foo as you may expect given the behaviour of the UNIX cp command. If you want that behaviour specify the destination as a subdirectory with the same name as source e.g. cp("src", "dest/src").
  • File to directory. If source is a file and dest is a pre-existing directory, it is copied into that directory with the same name, overwriting any file with the same name that's already there.
  • Creates directories. If source is a file and dest does not exist, destination is treated as a file and the directory it's in is created along with all parents.
  • Archives. If source is a directory and dest does not exist, then:
    • If the name ends in .zip or .jar the contents of source are zipped and placed in the file.
    • Otherwise, dest is created as a directory and then the behaviour is as normal for a directory->directory copy.
  • File overwrites. If source is a file and dest is a file in a pre-existing directory, it is copied to that given path exactly, replacing the file.
  • If source is a directory and dest is a pre-existing file, IllegalStateException is thrown.

mv

mv moves from to to, overwriting or merging with to if it exists.

This command has subtly different semantics to the shell mv command or Files.move. It allows you to move a directory 'over' another directory without throwing a DirectoryNotEmptyException. If to is a directory and isn't empty, then instead of it being moved into that directory with the same name, each file is moved individually with directories being created as necessary in the destination, giving a form of merge semantics. If you want to move a directory to a subdirectory of somewhere else, you should therefore re-specify the source directory name as the last component of the target path.

However, if you try to move a file to a directory then it will be moved into that directory with the same name.

Warning

Currently extended attributes won't be copied when doing a merge, nor is the move atomic.

Hashing

1
2
check(sha256("file") == hash("file", "SHA-2"))
echo(hash("file", "fingerprint"))    // non-cryptographic but much faster

Permissions

Permissions are mapped to their equivalents on Windows.

1
2
3
chmod("dir", "a+w", "a+wx")
val perms: Set<PosixFilePermission> = dir.permissions
echo(perms)

Archives

1
2
3
tar("dir/", "dir.tar.gz")
zip("dir/", "dir.zip")
unzip("thing.zip")

Finding things

1
2
3
val matches = find("**.txt")
val matches = find("dir/*.txt")
val matches = find("regex:[0-9]+.txt")

Disk usage

1
2
val bytesUsed1 = du()
val bytesUsedByPNGs = du(dir, { it.name.endsWith("*.png") })

Accounting of hard links can be controlled.

Extended attributes

Also works on Windows.

1
2
xattr("file", "key", "value")
val value = xattr("file", "key")

Open in a GUI app

1
2
open("whatever.html")
open("https://www.google.com")