Extract Parent Paths Reliably with dirname
You need the directory part of a path in a script, and ${var%/*} gets it wrong the moment there is a trailing slash. You will learn a repeatable way to strip the last component from a path with dirname, process several names in one call, and pick newline or NUL output for the next command in a pipeline. It takes about ten minutes. The examples use GNU dirname from coreutils 9.4, installed here as package version 9.4-3ubuntu6.3.
The route
Jump straight to the step you need, or tick off Done means at the end.
- You need: a shell and the coreutils package, which is normally already on a Linux system.
- What it touches: nothing. This guide only computes and prints path text. It does not create, rename, delete or inspect the files named in its arguments, so the ordinary examples need no elevated privileges.
1. Check the installed command
Confirm which executable your shell will run, then read its local help:
$ command -v dirname
/usr/bin/dirname
$ dirname --version | sed -n '1p'
dirname (GNU coreutils) 9.4
$ dirname --help | sed -n '1,12p'
Usage: dirname [OPTION] NAME...
Output each NAME with its last non-slash component and trailing slashes
removed; if NAME contains no /'s, output '.' (meaning the current directory).
-z, --zero end each output line with NUL, not newline
--help display this help and exit
--version output version information and exit
The command takes one or more NAME arguments. It removes the final non-slash component and any trailing slashes, then prints what is left. If a name has no slash, the documented result is ., meaning the current directory.
Checkpoint
If dirname --version reports a different implementation or version, keep the local help beside you. Option details and diagnostics can vary between implementations.
2. Extract the parent of one path
Pass the path as a single shell argument:
$ dirname /srv/www/site/index.html
/srv/www/site
This is a textual transformation. The path does not have to name an existing file, which makes it handy for building a destination, log name or configuration value. Check the result directly when the parent matters:
$ parent=$(dirname /srv/www/site/index.html)
$ printf 'parent=%s\n' "$parent"
parent=/srv/www/site
Quote the result when you use it later. The quotes keep a path containing whitespace as one argument:
$ input='/srv/Customer Files/report.csv'
$ parent=$(dirname "$input")
$ printf '%s\n' "$parent"
/srv/Customer Files
Tip
The printed parent is not a directory change. dirname never changes the shell's working directory. To work there, validate it with a separate command such as test -d, then use cd -- "$parent" on purpose.
3. Learn the edge cases before scripting
Trailing slashes are removed before the final component is selected:
$ dirname /usr/bin/
/usr
$ dirname foo/
.
$ dirname foo//bar///
foo
A name with no slash gives a dot, not an empty string:
$ dirname stdio.h
.
$ dirname report.csv
.
The root path stays the root path:
$ dirname /
/
$ dirname //
/
These results are easy to misread inside a bigger script:
- A result of
.means the current directory. It does not mean the input was invalid. - A result of
/is a real root path. Do not add another slash or treat it as a blank parent.
Checkpoint
Test your script's real input shapes with harmless names before you use the result to build a file operation. Include a bare filename, a path ending in /, and a path whose parent is /.
4. Process several names in one command
Multiple names give one output line per input, in the same order:
$ dirname /var/log/app/error.log /home/alice/notes/today.txt report.csv
/var/log/app
/home/alice/notes
.
That suits a short, controlled list of paths. Keep the arguments quoted if they come from shell variables:
$ first='/var/log/app/error.log'
$ second='/home/alice/notes/today.txt'
$ dirname "$first" "$second"
/var/log/app
/home/alice/notes
Warning
Do not use unquoted variables for arbitrary names. Word splitting can turn one path containing spaces into several arguments, and pathname expansion can swap wildcard characters for unrelated directory entries before dirname sees them.
5. Use NUL output for machine-readable batches
The default separator is a newline. That reads well in a terminal, but a newline can also be part of a Unix filename. GNU dirname has -z or --zero to end each result with a NUL byte instead:
$ dirname --zero /srv/app/log.txt report.csv | od -An -t x1
2f 73 72 76 2f 61 70 70 00 2e 00
The hexadecimal 00 bytes are the separators. Three rules follow:
- Use a NUL-aware consumer. Do not feed NUL-delimited output to a normal line-oriented loop and assume it is newline-delimited.
- Keep the stream in a pipe or file. Command substitution removes trailing newlines and cannot safely carry NUL-delimited data.
- Remember what
--zerodoes not do. It changes only the terminator. It does not quote or escape the path text.
For ordinary, trusted path lists, newline output is simpler. Reach for --zero when the next tool supports NUL records and the input may contain unusual characters.
6. Handle errors without guessing
Run the command with no arguments to see how missing input is reported, and capture the status straight away:
$ dirname
dirname: missing operand
Try 'dirname --help' for more information.
$ printf 'exit status: %s\n' "$?"
exit status: 1
The diagnostic wording can be translated or change between releases. What you can rely on is that the command writes an error and returns non-zero. Never let a failed command feed an empty or stale parent into a later file operation.
A path that does not exist is not an error for dirname:
$ dirname /path/that/does/not/exist/output.txt
/path/that/does/not/exist
If your workflow needs the parent to exist, check that separately. This read-only test stops before a later operation if the computed parent is absent:
parent=$(dirname -- "$input") || exit 1
if test ! -d "$parent"; then
printf 'parent directory is missing: %s\n' "$parent" >&2
exit 1
fi
The check creates nothing and needs no sudo.
Warning
If a later command will write, delete or move files, review that command separately before you run it. Those operations can change state even though dirname itself cannot.
Done means
- Version confirmed: you checked the installed GNU coreutils version and local option syntax.
- Parent extracted: you can get one parent path without changing directory or touching the named file.
- Edge cases known: a bare name returns
.and a root path returns/. - Quoting habit: you quote variable expansions and keep the input order for batches.
- NUL used safely: you use
--zeroonly with a consumer that understands NUL-delimited records. - Status checked: you look at
dirname's exit status before using its result in a state-changing command.