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.

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:

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:

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