Given a string file path such as /foo/fizzbuzz.bar
, how would I use bash to extract just the fizzbuzz
portion of said string?

- 46,058
- 19
- 106
- 116

- 66,744
- 41
- 126
- 187
-
Informations you can find in [Bash manual](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html#Shell-Parameter-Expansion), look for `${parameter%word}` and `${parameter%%word}` in trailing portion matching section. – 1ac0 Sep 20 '16 at 11:44
15 Answers
Here's how to do it with the # and % operators in Bash.
$ x="/foo/fizzbuzz.bar"
$ y=${x%.bar}
$ echo ${y##*/}
fizzbuzz
${x%.bar}
could also be ${x%.*}
to remove everything after a dot or ${x%%.*}
to remove everything after the first dot.
Example:
$ x="/foo/fizzbuzz.bar.quux"
$ y=${x%.*}
$ echo $y
/foo/fizzbuzz.bar
$ y=${x%%.*}
$ echo $y
/foo/fizzbuzz
Documentation can be found in the Bash manual. Look for ${parameter%word}
and ${parameter%%word}
trailing portion matching section.

- 53,022
- 10
- 79
- 131
-
I ended up using this one because it was the most flexible and there were a couple other similar things I wanted to do as well that this did nicely. – Redwood Oct 05 '08 at 07:37
-
This is probably the most flexible of all the answers posted, but I think the answers suggesting the basename and dirname commands deserve some attention as well. They may be just the trick if you don't need any other fancy pattern matching. – mgadda Dec 04 '12 at 02:08
-
7
-
16@Basil: Parameter Expansion. On a console type "man bash" and then type "/parameter expansion" – Zan Lynx Apr 11 '13 at 18:02
-
1I guess the 'man bash' explanation makes sense if you already know what it does or if you tried it out yourself the hard way. It's almost as bad as git reference. I'd just google it instead. – triplebig Sep 28 '16 at 05:37
-
Is there a way to do this without using the `x` variable? If I try `echo ${"/foo/fizzbuzz.bar.quux"%.*}` I get `-bash: ${"/foo/fizzbuzz.bar.quux"%.*}: bad substitution` – Alec Jacobson Aug 27 '19 at 15:23
-
@AlecJacobson No because without a variable it would be silly; you would just write it out as it is supposed to be in the first place. – Zan Lynx Aug 28 '19 at 03:43
-
In a https://en.wikipedia.org/wiki/Metaprogramming context you might not be able to. Is the answer really "no"? Or did you assume it's no because you thought it'd be silly? – Alec Jacobson Aug 28 '19 at 23:24
-
@AlecJacobson With metaprogramming it is even more silly because the generator program should process it first and write it out as the correct literal string. If it could not be a literal constant then it would be a *variable*. – Zan Lynx Aug 28 '19 at 23:29
-
Sometimes it would be convenient to be able to pass in a string directly to a parameter expansion, without using a variable; but no, the shell does not suppert that. – tripleee Aug 23 '21 at 13:20
-
Slightly more convenient than @ZanLynx: On a console type "man bash" and then type `/^ +parameter expansion` – ijoseph Dec 04 '22 at 04:15
-
look at the basename command:
NAME="$(basename /foo/fizzbuzz.bar .bar)"
instructs it to remove the suffix .bar
, results in NAME=fizzbuzz

- 62,887
- 36
- 269
- 388

- 14,573
- 6
- 35
- 54
-
13Probably the simplest of all the currently offered solutions... although I'd use $(...) instead of backticks. – Michael Johnson Sep 24 '08 at 04:20
-
7Simplest but adds a dependency (not a huge or weird one, I admit). It also needs to know the suffix. – Vinko Vrsalovic Sep 24 '08 at 05:25
-
And can be used to remove anything from the end, basically it does just a string removal from end. – Smar Aug 25 '11 at 15:43
-
4The problem is the time hit. I just searched the question for this discussion after watching bash take almost 5min to process 800 files, using basename. Using the above regex method, the time was reduced to about 7sec. Though this answer is easier to perform for the programmer, the time hit is just too much. Imagine a folder with a couple thousand files in it! I have some such folders. – xizdaqrian Aug 14 '16 at 22:11
-
2@xizdaqrian This is absolutely false. This is a simple program, which shouldn't take half a second to return. I just executed time find /home/me/dev -name "*.py" .py -exec basename {} \; and it stripped the extension and directory for 1500 files in 1 second total. – Laszlo Treszkai Dec 12 '19 at 20:32
-
2The general idea to avoid an external process whenever you can is sound, though. and a basic tenet of shell programming. – tripleee Aug 23 '21 at 13:23
-
Pure bash, done in two separate operations:
Remove the path from a path-string:
path=/foo/bar/bim/baz/file.gif file=${path##*/} #$file is now 'file.gif'
Remove the extension from a path-string:
base=${file%.*} #${base} is now 'file'.

- 50,338
- 35
- 112
- 199

- 711
- 5
- 3
Using basename I used the following to achieve this:
for file in *; do
ext=${file##*.}
fname=`basename $file $ext`
# Do things with $fname
done;
This requires no a priori knowledge of the file extension and works even when you have a filename that has dots in it's filename (in front of it's extension); it does require the program basename
though, but this is part of the GNU coreutils so it should ship with any distro.

- 209
- 2
- 3
-
1Excellent answer! removes the extension in a very clean way, but it doesn't remove the . at the end of the filename. – metrix Apr 23 '14 at 01:04
-
4@metrix just add the "." before $ext, ie: `fname=\`basename $file .$ext\`` – Carlos Troncoso May 14 '16 at 06:29
-
This could do bad things if there are spaces in the filenames. You'll should wrap `$file`, `$ext`, and the backticked section (including the backticks themselves) in double quotes. – mwfearnley Aug 16 '20 at 15:04
-
Downvote. This doesn't work right at all for me. It's inserting junk newlines into my filenames and not removing the extensions. Not even remotely usable. – John Smith Sep 28 '22 at 22:39
The basename and dirname functions are what you're after:
mystring=/foo/fizzbuzz.bar
echo basename: $(basename "${mystring}")
echo basename + remove .bar: $(basename "${mystring}" .bar)
echo dirname: $(dirname "${mystring}")
Has output:
basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo

- 41,746
- 15
- 73
- 90
-
2It would be helpful to fix the quoting here -- maybe run this through http://shellcheck.net/ with `mystring=$1` rather than the current constant value (which will suppress several warnings, being certain not to contain spaces/glob characters/etc), and address the issues it finds? – Charles Duffy Oct 24 '18 at 21:43
-
1Well, I made some appropriate changes to support quotation marks in $mystring. Gosh this was a long time ago I wrote this :) – Jerub Feb 11 '19 at 00:26
-
1Would be further improvement to quote the results: `echo "basename: $(basename "$mystring")"` -- that way if `mystring='/foo/*'` you don't get the `*` replaced with a list of files in the current directory *after* `basename` finishes. – Charles Duffy Feb 11 '19 at 00:32
Pure bash way:
~$ x="/foo/bar/fizzbuzz.bar.quux.zoom";
~$ y=${x/\/*\//};
~$ echo ${y/.*/};
fizzbuzz
This functionality is explained on man bash under "Parameter Expansion". Non bash ways abound: awk, perl, sed and so on.
EDIT: Works with dots in file suffixes and doesn't need to know the suffix (extension), but doesn’t work with dots in the name itself.

- 40,865
- 11
- 112
- 103

- 330,807
- 53
- 334
- 373
Using basename
assumes that you know what the file extension is, doesn't it?
And I believe that the various regular expression suggestions don't cope with a filename containing more than one "."
The following seems to cope with double dots. Oh, and filenames that contain a "/" themselves (just for kicks)
To paraphrase Pascal, "Sorry this script is so long. I didn't have time to make it shorter"
#!/usr/bin/perl
$fullname = $ARGV[0];
($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
print $basename . "\n";

- 39,594
- 3
- 35
- 61
In addition to the POSIX conformant syntax used in this answer,
basename string [suffix]
as in
basename /foo/fizzbuzz.bar .bar
GNU basename
supports another syntax:
basename -s .bar /foo/fizzbuzz.bar
with the same result. The difference and advantage is that -s
implies -a
, which supports multiple arguments:
$ basename -s .bar /foo/fizzbuzz.bar /baz/foobar.bar
fizzbuzz
foobar
This can even be made filename-safe by separating the output with NUL bytes using the -z
option, for example for these files containing blanks, newlines and glob characters (quoted by ls
):
$ ls has*
'has'$'\n''newline.bar' 'has space.bar' 'has*.bar'
Reading into an array:
$ readarray -d $'\0' arr < <(basename -zs .bar has*)
$ declare -p arr
declare -a arr=([0]=$'has\nnewline' [1]="has space" [2]="has*")
readarray -d
requires Bash 4.4 or newer. For older versions, we have to loop:
while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)

- 46,058
- 19
- 106
- 116
-
Also, the suffix specified is removed in the output **if** present (and ignored otherwise). – aksh1618 Jan 14 '19 at 12:12
If you can't use basename as suggested in other posts, you can always use sed. Here is an (ugly) example. It isn't the greatest, but it works by extracting the wanted string and replacing the input with the wanted string.
echo '/foo/fizzbuzz.bar' | sed 's|.*\/\([^\.]*\)\(\..*\)$|\1|g'
Which will get you the output
fizzbuzz

- 77
- 5
-
Although this is the answer to the original question, this command is useful when I have lines of paths in a file to extract base names to print them out to the screen. – Sangcheol Choi Sep 03 '13 at 19:42
Beware of the suggested perl solution: it removes anything after the first dot.
$ echo some.file.with.dots | perl -pe 's/\..*$//;s{^.*/}{}'
some
If you want to do it with perl, this works:
$ echo some.file.with.dots | perl -pe 's/(.*)\..*$/$1/;s{^.*/}{}'
some.file.with
But if you are using Bash, the solutions with y=${x%.*}
(or basename "$x" .ext
if you know the extension) are much simpler.

- 13,452
- 5
- 76
- 69
If you want to keep just the filename with extension and strip the file path
$ x="myfile/hello/foo/fizzbuzz.bar"
$ echo ${x##*/}
$ fizzbuzz.bar
Explanation in Bash manual, see ${parameter##word}

- 1,043
- 10
- 13
Combining the top-rated answer with the second-top-rated answer to get the filename without the full path:
$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz

- 4,740
- 1
- 20
- 14
-
2
-
Also, [broken quoting.](https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable) – tripleee Aug 23 '21 at 13:25
The basename does that, removes the path. It will also remove the suffix if given and if it matches the suffix of the file but you would need to know the suffix to give to the command. Otherwise you can use mv and figure out what the new name should be some other way.

- 72
- 1
- 4
You can use
mv *<PATTERN>.jar "$(basename *<PATTERN>.jar <PATTERN>.jar).jar"
For e.g:- I wanted to remove -SNAPSHOT
from my file name. For that used below command
mv *-SNAPSHOT.jar "$(basename *-SNAPSHOT.jar -SNAPSHOT.jar).jar"

- 105
- 9
-
2The wildcards here are horribly wrong unless you have only exactly one matching file. – tripleee Aug 23 '21 at 13:21