The Path Not Taken

Photo by Matt Duncan on Unsplash

The Path Not Taken

A quick primer on dealing with file paths in Julia

No matter what kind of code base you end up working on, there's always some logic that has to deal with file paths. Sometimes we need to know the location of a certain file or a certain folder. On other occasions, we might need to create a path to a folder. Also, it's usually considered a bad practice to use hard-coded paths in your code, especially when it's going to be deployed elsewhere. For such needs, you should rely on programmatically generating relative paths.

Julia provides a lot of built-in functionality to interact with the host filesystem. Let's have a look at some of them with useful examples.

Print working directory

julia> pwd()
"/home/vikas/Downloads"

Get the contents of a directory

julia> readdir(pwd())
16-element Vector{String}:
 "0001-pinctrl-sunrisepoint-Add-missing-Interrupt-Status-re.patch"
 "20230423_150150.jpg"
 "20230423_170112.jpg"
 "20230425_222307.jpg"
 "20230428_233929.jpg"
 "BIOS_IMG.rcv"
 "DALL·E 2023-05-01 20.53.45 - An"92 bytes ⋯ "and some code on the screen.png"
 "DALL·E 2023-05-01 23.03.10 - An"56 bytes ⋯ "track with fire behind them.png"
...

Notice that readdir() returns an array of strings. This immediately allows us to perform additional operations such as filtering for a given file type:

julia> filter(x -> endswith(x, ".jpg"), readdir(pwd()))
4-element Vector{String}:
 "20230423_150150.jpg"
 "20230423_170112.jpg"
 "20230425_222307.jpg"
 "20230428_233929.jpg"

Using the join keyword, full paths to the files can be determined:

julia> readdir(join = true)
17-element Vector{String}:
 "/home/vikas/Downloads/0001-pinc"23 bytes ⋯ "ssing-Interrupt-Status-re.patch"
 "/home/vikas/Downloads/20230423_150150.jpg"
 "/home/vikas/Downloads/20230423_170112.jpg"
 "/home/vikas/Downloads/20230425_222307.jpg"
 "/home/vikas/Downloads/20230428_233929.jpg"
 "/home/vikas/Downloads/BIOS_IMG.rcv"
...

Check if a file exists

julia> isfile("julia-logo-color.png")
true

Check if a directory exists

julia> isdir("amazing_blog")
false

julia> mkdir("amazing_blog")
"amazing_blog"

julia> isdir("amazing_blog")
true

Get directory name from path

julia> current_location = pwd()
"/home/vikas/Downloads"

julia> dirname(current_location)
"/home/vikas"

julia> basename(current_location)
"Downloads"

Create a deep nested directory

mkpath can be used to create intermediate folders to a deeply nested path as shown below:

julia> mkpath(["amazing_blog", "folder_1", "folder_11", "folder_111"] |> joinpath)
"amazing_blog/folder_1/folder_11/folder_111"

julia> isdir("amazing_blog/folder_1/folder_11/folder_111")
true

Split a path

Sometimes, it's needed to extract some useful information from a given path. For such cases, splitpath is your friend:

julia> splitpath(current_location)
4-element Vector{String}:
 "/"
 "home"
 "vikas"
 "Downloads"

Join a path

julia> parts = splitpath(current_location)
4-element Vector{String}:
 "/"
 "home"
 "vikas"
 "Downloads"

julia> joinpath(parts)
"/home/vikas/Downloads"

This is extremely useful since the path separator is determined based on the operating system (Linux or Windows). In case you do need to specify a path in a code base that will be used both on Linux and Windows, it's way more robust to make use of joinpath.

Get current directory w.r.t. a file

The @__DIR__ macro can be used to determine the path to a file from where it's called. This is useful to safely add a relative path to a file or folder for which you are certain that the location would remain unchanged.

julia> pwd()
"/home/vikas/Downloads/amazing_blog"

julia> joinpath(@__DIR__, "..", "20230425_222307.jpg")
"/home/vikas/Downloads/amazing_blog/../20230425_222307.jpg"

julia> joinpath(@__DIR__, "..", "20230425_222307.jpg") |> isfile
true

Traversing through a directory tree

Using walkdir, we can surf through all the files within a folder, and perform operations such as finding files that match a certain name. For example, let's find files that have "README" in the name:

julia> pwd()
"/home/vikas"

julia> for (root, dirs, files) in walkdir("Desktop")
          for file in files
              if occursin("READ", file)
                  joinpath(root, file) |> println
              end
          end
       end
Desktop/Julia_release/julia-1.8.5/share/julia/stdlib/v1.8/ArgTools/README.md
Desktop/Julia_release/julia-1.8.5/share/julia/stdlib/v1.8/Downloads/README.md
Desktop/Julia_release/julia-1.8.5/share/julia/stdlib/v1.8/LibCURL/README.md
...

Conclusion

Julia has excellent support for handling paths to files and folders. The above examples are some of my most routinely used functions. A more comprehensive list can be found in the official documentation. I hope you learned something new today. Don't forget to bookmark this post and share it with fellow Julia nerds.