Filehandles, files, directories

chdir#

Change the process’s current working directory.

chdir is the Perl wrapper around the chdir(2) and — where the kernel supports it — fchdir(2) system calls. It changes the directory that relative paths are resolved against for the current process and, on success, persists that change for every subsequent file operation in the program.

Synopsis#

chdir EXPR
chdir FILEHANDLE
chdir DIRHANDLE
chdir

What you get back#

1 on success, 0 on failure. On failure $! is set to the underlying errno so you can distinguish “no such directory” (ENOENT) from “permission denied” (EACCES) from “not a directory” (ENOTDIR):

chdir $path
    or die "chdir $path failed: $!";

The return is always a plain boolean — there is no separate “changed to nowhere because no argument was given” value. The no-argument form either succeeds by changing to the home directory or fails like any other call.

Global state it touches#

  • %ENV — read when chdir is called with no argument. The lookup order is $ENV{HOME} first, then $ENV{LOGDIR}. If neither is set, chdir does nothing and returns 0.

  • $! — set on failure to the errno reported by the kernel.

chdir does not update $ENV{PWD}. Shells maintain PWD themselves; a Perl program that cares about PWD for child processes has to assign to $ENV{PWD} after a successful chdir.

Argument forms#

  • chdir EXPREXPR is stringified and passed to chdir(2) as a pathname. Relative paths are resolved against the current working directory at the moment of the call.

  • chdir FILEHANDLE and chdir DIRHANDLE — on Linux (and any system with fchdir(2)) the kernel changes directory to the one the handle refers to. FILEHANDLE here means a filehandle opened on a directory, or a directory handle from opendir. Using the handle form avoids a TOCTOU race between resolving a pathname and changing into it.

  • chdir (no argument) — changes to $ENV{HOME}, falling back to $ENV{LOGDIR} if HOME is unset. If both are unset the call fails and $! is left untouched; check the return value, not $!.

Examples#

Change into a directory and bail out if it does not exist:

chdir "/var/log"
    or die "cannot enter /var/log: $!";

Run a block of work in a different directory and return to where you started:

use Cwd qw(getcwd);
my $origin = getcwd;
chdir $work_dir or die "chdir $work_dir: $!";
# ... do work relative to $work_dir ...
chdir $origin  or die "chdir $origin: $!";

Change via a directory handle (race-free on systems with fchdir(2)):

opendir my $dh, $path or die "opendir $path: $!";
chdir $dh              or die "fchdir $path: $!";

Go home:

chdir or die "no HOME/LOGDIR in environment";

Probe a list of candidates, stopping at the first that works:

for my $d ("/srv/app", "/opt/app", "/usr/local/app") {
    last if chdir $d;
}

Edge cases#

  • Empty string or undef: chdir "" and chdir undef both fail and set $! to ENOENT. They do not fall back to $HOME — the no-argument form is triggered by absence of an argument, not by an argument that happens to be empty.

  • Relative paths on a thread with its own cwd: the kernel’s current working directory is per-process, not per-thread, on Linux. Two threads calling chdir concurrently race.

  • Symlink to a directory: chdir follows symlinks, landing in the target. Use readlink or Cwd::abs_path first if you need to know the real destination.

  • Deleted directory: if the directory was removed after you entered it, relative operations from inside fail with ENOENT and most shells show the path as (deleted). You can still chdir out with an absolute path.

  • Handle form on systems without fchdir(2): raises an exception. Linux always supports it; the restriction is a portability concern for code that also runs on older systems.

  • Untrusted input: passing a user-supplied path to chdir without validation lets the caller redirect all subsequent relative-path operations in the program. Canonicalise with Cwd::abs_path and check against an allow-list before the call if the input crosses a trust boundary.

  • After fork: the child inherits the parent’s cwd. A chdir in the child does not affect the parent.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • mkdir — create a directory; pair with chdir when building a workspace and stepping into it

  • rmdir — remove a directory; do not rmdir the cwd, chdir out first

  • opendir — open a directory handle usable as the argument to chdir for race-free directory changes

  • chroot — stronger confinement than chdir; changes the filesystem root for the process, not just the cwd

  • $ENV{HOME} — consulted by the no-argument form of chdir before falling back to $ENV{LOGDIR}

  • $! — carries the errno when chdir returns 0