Skip to content
shellmap

dirnameGet the directory portion of a path (everything except the leaf) across all 5 shells

Equivalents in every shell

Bashunix
dirname /var/log/syslog

Prints `/var/log`. Returns `.` when the input has no directory component (e.g. `dirname file.txt` → `.`).

Zshunix
dirname /var/log/syslog

Same external binary. Zsh `:h` modifier is the builtin: `${path:h}` returns the head without forking. Chain modifiers: `${path:h:t}` returns the parent directory's name.

Fishunix
dirname /var/log/syslog

Same external binary. Fish `path dirname /var/log/syslog` is the builtin (fish 3.5+), handles multiple paths in one call.

PowerShellwindows
Split-Path -Parent "C:\Windows\System32\drivers\etc\hosts"

`-Parent` is the default — `Split-Path "<path>"` is equivalent. Returns empty string on bare filenames like `Split-Path "foo.txt"` → `` (NOT `.`). `[IO.Path]::GetDirectoryName(...)` is the .NET equivalent and behaves the same.

cmd.exewindows
for %I in ("C:\Windows\System32\drivers\etc\hosts") do @echo %~dpI

No native `dirname`. `%~dp` returns drive+path (with trailing backslash). `%~p` is path-only without drive letter.

Worked examples

Get the directory containing a known file

Bash
dirname /etc/nginx/nginx.conf
Fish
path dirname /etc/nginx/nginx.conf
PowerShell
Split-Path "/etc/nginx/nginx.conf" -Parent
cmd.exe
for %I in ("C:\nginx\nginx.conf") do @echo %~dpI

`cd` into the directory of the currently-running script

Bash
cd "$(dirname "$0")"
Fish
cd (path dirname (status filename))
PowerShell
Set-Location (Split-Path -Parent $MyInvocation.MyCommand.Path)
cmd.exe
cd /d "%~dp0"

Get the grandparent directory (two levels up)

Bash
dirname "$(dirname /a/b/c/d)"
Zsh
echo ${path:h:h}  # where path=/a/b/c/d
PowerShell
Split-Path -Parent (Split-Path -Parent "/a/b/c/d")
cmd.exe
for %I in ("C:\a\b\c\d") do for %J in ("%~dpI.") do @echo %~dpJ

Gotchas

  • `dirname file.txt` (no slash in input) returns `.`, NOT the empty string — scripts that test `[ -z "$(dirname "$f")" ]` will never fire. Test for `.` explicitly or use absolute paths upstream.
  • `dirname /` returns `/`, and `dirname //` returns `//` (POSIX preserves a leading `//` for implementation-defined behaviour). `dirname ""` returns `.` on GNU coreutils, `` (empty) on BSD — never pass empty string and rely on consistent output.
  • PowerShell `Split-Path -Parent "foo.txt"` returns the empty string (not `.`). Code that ports `dirname "$f" || echo .` to pwsh literally will mishandle the empty case. Defensive form: `(Split-Path -Parent $f) -or "."` doesn't work either (`-or` on string returns boolean). Use `if (-not $d) { $d = "." }` after the call.
  • `cd "$(dirname "$0")"` is the idiomatic "go to my script's dir" but it follows symlinks differently than expected. For the canonical absolute path use `cd "$(cd "$(dirname "$0")" && pwd)"` or `dirname "$(readlink -f "$0")"` (GNU) — on macOS `readlink -f` is missing, install coreutils or use `python3 -c "import os,sys;print(os.path.realpath(sys.argv[1]))" "$0"`.
  • cmd `%~dp0` from a script always ends with a trailing backslash (e.g. `C:\Users\maggie\`) — concatenating with another path produces double slashes (`C:\Users\maggie\\file.txt`). Windows APIs mostly tolerate it, but UNC paths and PowerShell sub-calls can break. Strip with `set p=%~dp0` then `set p=%p:~0,-1%`.

WSL & PowerShell Core notes

pwsh`Split-Path -Parent` is cross-platform and works identically on Windows / Linux / macOS pwsh. The `.NET [IO.Path]::GetDirectoryName(...)` does too. Empty-string-on-bare-filename behaviour applies on every OS — port carefully from bash `dirname`.
WSLWSL `dirname` is GNU coreutils, identical to Linux. Mixed-path scripts that produce `/mnt/c/...` (WSL) and `C:\...` (Windows) addressing the same directory: normalise with `wslpath -w` (WSL → Windows) or `wslpath -u` (Windows → WSL) before comparing.

Related commands