A build script hands you /srv/app/releases/v4.2.tar.gz and you just want v4.2 for a log line: that is what basename is for. This guide gets you a small, reliable pattern for turning a path into a filename, dropping a known suffix, and feeding several results to another command. The examples use GNU basename 9.4 from the Ubuntu coreutils package installed here as version 9.4-3ubuntu6.3.
Allow about ten minutes. You need a shell and the ordinary basename command. Nothing here needs elevated privileges, and the examples only read names and write output to the terminal. The command does not rename, copy or delete files.
Check the binary and package before relying on a feature in a script:
$ command -v basename
/usr/bin/basename
$ basename --version | head -1
basename (GNU coreutils) 9.4
$ dpkg-query -W -f='\${Package} \${Version}\n' coreutils
coreutils 9.4-3ubuntu6.3
Your package revision may differ even when the upstream coreutils version is the same. The installed manual page is the contract used for the rest of this guide. If you are on another Unix, read its local help with basename --help instead, because option sets and details can vary.
Pass one name to get the final component. This is text processing only: the path does not have to exist.
$ basename /srv/app/releases/v4.2.tar.gz
v4.2.tar.gz
$ basename ./reports/weekly.txt
weekly.txt
basename removes leading directory components and leaves the rest unchanged; a missing path produces a result just like an existing one.Checkpoint: if your script needs to act on the file rather than just display its name, test that separately with an explicit file check:
path='/srv/app/releases/v4.2.tar.gz'
if [ -e "$path" ]; then
printf 'found: %s\n' "$(basename "$path")"
else
printf 'missing: %s\n' "$path" >&2
exit 1
fi
Keep the path in a quoted variable. Quoting stops spaces, wildcards and shell metacharacters in the input becoming extra shell syntax.
Give a second argument when the filename has a known suffix:
$ basename include/stdio.h .h
stdio
$ basename notes.txt .md
notes.txt
The suffix is removed only when it sits at the end of the name, after directory components have already been stripped. It is not a pattern: .md does not touch the similar-looking part of notes.txt.
For a variable suffix, quote both arguments:
path='/srv/app/releases/v4.2.tar.gz'
suffix='.tar.gz'
stem=$(basename "$path" "$suffix")
printf 'stem: %s\n' "$stem"
# stem: v4.2
Command substitution strips trailing newlines from the output, which is exactly what you want for a filename component. Do not use this form for arbitrary records that may contain embedded newlines; use a NUL-delimited pipeline instead, covered in step 5.
The plain one-name form is easy to misread once more arguments are involved. Use -a or --multiple to say that every remaining argument is a name:
$ basename --multiple /srv/app/one.log /srv/app/two.log /srv/app/three.log
one.log
two.log
three.log
-s or --suffix also switches on multiple-name mode, which is convenient for applying one suffix to a list:
$ basename --suffix=.log /var/log/app.log /var/log/worker.log
app
worker
Tip: do not confuse basename NAME SUFFIX with a two-item list. Without -a or -s, the second argument is the suffix for the single first name, which is a common trap when a command gets built up incrementally.
Newline-delimited output is readable, but a filename can legally contain a newline. When another program will consume the output, use -z or --zero so each result ends with a NUL byte:
$ basename -z -a /tmp/one.txt /tmp/two.txt | od -An -t x1
6f 6e 65 2e 74 78 74 00 74 77 6f 2e 74 78 74 00
The two 00 bytes are the record separators. Pair this mode with a NUL-aware consumer, such as GNU xargs -0:
basename -z -a /srv/app/one.log /srv/app/two.log |
xargs -0 -r printf 'name: <%s>\n'
Expected output is:
name: <one.log>
name: <two.log>
Warning: this protects the boundary between records, but it does not make an unsafe downstream command safe. Quote shell variables, avoid building a shell command from untrusted names, and stop to review the whole pipeline before anything that deletes or overwrites files. Nothing in these examples changes filesystem state.
Capture the output when you need to use it later, and keep the input and suffix separate:
path='/srv/app/releases/v4.2.tar.gz'
name=$(basename "$path" '.tar.gz')
if [ "$name" = 'v4.2' ]; then
printf 'release: %s\n' "$name"
else
printf 'unexpected name: %s\n' "$name" >&2
exit 1
fi
Expected output:
release: v4.2
Most failures here are shell errors rather than basename errors: an unquoted variable splitting into several arguments, an empty suffix that does not express the rule you intended, or a command substitution hiding a failing pipeline if you only look at its text. Keep transformations small and check the value at the point where it matters.
-a or -s when processing several names.-z with a NUL-aware consumer when newlines could be part of a name.