Extract file basename without path and extension in bash

LinuxBashUnixFilenames

Linux Problem Overview


Given file names like these:

/the/path/foo.txt
bar.txt

I hope to get:

foo
bar

Why this doesn't work?

#!/bin/bash

fullfile=$1
fname=$(basename $fullfile)
fbname=${fname%.*}
echo $fbname

What's the right way to do it?

Linux Solutions


Solution 1 - Linux

You don't have to call the external basename command. Instead, you could use the following commands:

$ s=/the/path/foo.txt
$ echo "${s##*/}"
foo.txt
$ s=${s##*/}
$ echo "${s%.txt}"
foo
$ echo "${s%.*}"
foo

Note that this solution should work in all recent (post 2004) POSIX compliant shells, (e.g. bash, dash, ksh, etc.).

Source: Shell Command Language 2.6.2 Parameter Expansion

More on bash String Manipulations: http://tldp.org/LDP/LG/issue18/bash.html

Solution 2 - Linux

The http://opengroup.org/onlinepubs/007908799/xcu/basename.html">basename</a> command has two different invocations; in one, you specify just the path, in which case it gives you the last component, while in the other you also give a suffix that it will remove. So, you can simplify your example code by using the second invocation of basename. Also, be careful to correctly quote things:

fbname=$(basename "$1" .txt)
echo "$fbname"

Solution 3 - Linux

A combination of basename and cut works fine, even in case of double ending like .tar.gz:

fbname=$(basename "$fullfile" | cut -d. -f1)

Would be interesting if this solution needs less arithmetic power than Bash Parameter Expansion.

Solution 4 - Linux

Here are oneliners:

  1. $(basename "${s%.*}")
  2. $(basename "${s}" ".${s##*.}")

I needed this, the same as asked by bongbang and w4etwetewtwet.

Solution 5 - Linux

Pure bash, no basename, no variable juggling. Set a string and echo:

p=/the/path/foo.txt
echo "${p//+(*\/|.*)}"

Output:

foo

Note: the bash extglob option must be "on", (Ubuntu sets extglob "on" by default), if it's not, do:

shopt -s extglob

Walking through the ${p//+(*\/|.*)}:

  1. ${p -- start with $p.
  2. // substitute every instance of the pattern that follows.
  3. +( match one or more of the pattern list in parenthesis, (i.e. until item #7 below).
  4. 1st pattern: *\/ matches anything before a literal "/" char.
  5. pattern separator | which in this instance acts like a logical OR.
  6. 2nd pattern: .* matches anything after a literal "." -- that is, in bash the "." is just a period char, and not a regex dot.
  7. ) end pattern list.
  8. } end parameter expansion. With a string substitution, there's usually another / there, followed by a replacement string. But since there's no / there, the matched patterns are substituted with nothing; this deletes the matches.

Relevant man bash background:

  1. pattern substitution:

> ${parameter/pattern/string} > Pattern substitution. The pattern is expanded to produce a pat > tern just as in pathname expansion. Parameter is expanded and > the longest match of pattern against its value is replaced with > string. If pattern begins with /, all matches of pattern are > replaced with string. Normally only the first match is > replaced. If pattern begins with #, it must match at the begin‐ > ning of the expanded value of parameter. If pattern begins with > %, it must match at the end of the expanded value of parameter. > If string is null, matches of pattern are deleted and the / fol > lowing pattern may be omitted. If parameter is @ or *, the sub > stitution operation is applied to each positional parameter in > turn, and the expansion is the resultant list. If parameter is > an array variable subscripted with @ or *, the substitution > operation is applied to each member of the array in turn, and > the expansion is the resultant list.

  1. extended pattern matching:

> If the extglob shell option is enabled using the shopt builtin, several > extended pattern matching operators are recognized. In the following > description, a pattern-list is a list of one or more patterns separated > by a |. Composite patterns may be formed using one or more of the fol > lowing sub-patterns: > > ?(pattern-list) > Matches zero or one occurrence of the given patterns > *(pattern-list) > Matches zero or more occurrences of the given patterns > +(pattern-list) > Matches one or more occurrences of the given patterns > @(pattern-list) > Matches one of the given patterns > !(pattern-list) > Matches anything except one of the given patterns

Solution 6 - Linux

Here is another (more complex) way of getting either the filename or extension, first use the rev command to invert the file path, cut from the first . and then invert the file path again, like this:

filename=`rev <<< "$1" | cut -d"." -f2- | rev`
fileext=`rev <<< "$1" | cut -d"." -f1 | rev`

Solution 7 - Linux

If you want to play nice with Windows file paths (under Cygwin) you can also try this:

fname=${fullfile##*[/|\\]}

This will account for backslash separators when using BaSH on Windows.

Solution 8 - Linux

Just an alternative that I came up with to extract an extension, using the posts in this thread with my own small knowledge base that was more familiar to me.

ext="$(rev <<< "$(cut -f "1" -d "." <<< "$(rev <<< "file.docx")")")"

Note: Please advise on my use of quotes; it worked for me but I might be missing something on their proper use (I probably use too many).

Solution 9 - Linux

Use the basename command. Its manpage is here: http://unixhelp.ed.ac.uk/CGI/man-cgi?basename

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionneversaintView Question on Stackoverflow
Solution 1 - Linuxghostdog74View Answer on Stackoverflow
Solution 2 - LinuxMichael Aaron SafyanView Answer on Stackoverflow
Solution 3 - Linuxkom limView Answer on Stackoverflow
Solution 4 - Linuxsancho.s ReinstateMonicaCellioView Answer on Stackoverflow
Solution 5 - LinuxagcView Answer on Stackoverflow
Solution 6 - LinuxhiguaroView Answer on Stackoverflow
Solution 7 - Linuxvincent gravitasView Answer on Stackoverflow
Solution 8 - LinuxDiomoidView Answer on Stackoverflow
Solution 9 - LinuxBandicootView Answer on Stackoverflow