Categories
bash filenames string

Extract filename and extension in Bash

2587

I want to get the filename (without extension) and the extension separately.

The best solution I found so far is:

NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`

This is wrong because it doesn’t work if the file name contains multiple . characters. If, let’s say, I have a.b.js, it will consider a and b.js, instead of a.b and js.

It can be easily done in Python with

file, ext = os.path.splitext(path)

but I’d prefer not to fire up a Python interpreter just for this, if possible.

Any better ideas?

8

  • This question explains this bash technique and several other related ones.

    Jun 12, 2009 at 20:34

  • 35

    When applying the great answers below, do not simply paste in your variable like I show here Wrong: extension="{$filename##*.}" like I did for a while! Move the $ outside the curlys: Right: extension="${filename##*.}"

    – Krista K

    Aug 7, 2013 at 18:51


  • 4

    This is clearly a non-trivial problem and for me it is hard to tell if the answers below are completely correct. It’s amazing this is not a built in operation in (ba)sh (answers seem to implement the function using pattern matching). I decided to use Python’s os.path.splitext as above instead…

    Oct 1, 2015 at 8:01

  • 1

    As extension have to represent nature of a file, there is a magic command which check file to divine his nature and offert standard extension. see my answer

    Oct 14, 2016 at 8:02

  • 4

    The question is problematic in the first place because.. From the perspective of the OS and unix file-systems in general, there is no such thing as a file extension. Using a “.” to separate parts is a human convention, that only works as long as humans agree to follow it. For example, with the ‘tar’ program, it could have been decided to name output files with a “tar.” prefix instead of a “.tar” suffix — Giving “tar.somedir” instead of “somedir.tar”. There is no “general, always works” solution because of this–you have to write code that matches your specific needs and expected filenames.

    – C. M.

    Oct 10, 2018 at 0:08


4118

First, get file name without the path:

filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"

Alternatively, you can focus on the last “https://stackoverflow.com/” of the path instead of the ‘.’ which should work even if you have unpredictable file extensions:

filename="${fullfile##*/}"

You may want to check the documentation :

17

  • 92

    Check out gnu.org/software/bash/manual/html_node/… for the full feature set.

    – D.Shawley

    Jun 8, 2009 at 14:08

  • 27

    Add some quotes to “$fullfile”, or you’ll risk breaking the filename.

    – lhunath

    Jun 8, 2009 at 14:34

  • 51

    Heck, you could even write filename=”${fullfile##*/}” and avoid calling an extra basename

    – ephemient

    Jun 9, 2009 at 17:52

  • 53

    This “solution” does not work if the file does not have an extension — instead, the whole file name is output, which is quite bad considering that files without extensions are omnipresent.

    – nccc

    Jul 1, 2012 at 3:42


  • 47

    Fix for dealing with file names without extension: extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo ''). Note that if an extension is present, it will be returned including the initial ., e.g., .txt.

    – mklement0

    Sep 7, 2012 at 14:41

969

~% FILE="example.tar.gz"

~% echo "${FILE%%.*}"
example

~% echo "${FILE%.*}"
example.tar

~% echo "${FILE#*.}"
tar.gz

~% echo "${FILE##*.}"
gz

For more details, see shell parameter expansion in the Bash manual.

9

  • 30

    You (perhaps unintentionally) bring up the excellent question of what to do if the “extension” part of the filename has 2 dots in it, as in .tar.gz… I’ve never considered that issue, and I suspect it’s not solvable without knowing all the possible valid file extensions up front.

    – rmeador

    Jun 8, 2009 at 14:50

  • 10

    Why not solvable? In my example, it should be considered that the file contains two extensions, not an extension with two dots. You handle both extensions separately.

    – Juliano

    Jun 8, 2009 at 15:20

  • 29

    It is unsolvable on a lexical basis, you’ll need to check the file type. Consider if you had a game called dinosaurs.in.tar and you gzipped it to dinosaurs.in.tar.gz 🙂

    – porges

    Jun 13, 2009 at 9:11

  • 13

    This gets more complicated if you are passing in full paths. One of mine had a ‘.’ in a directory in the middle of the path, but none in the file name. Example “a/b.c/d/e/filename” would wind up “.c/d/e/filename”

    Mar 5, 2012 at 18:49


  • 14

    clearly no x.tar.gz‘s extension is gz and the filename is x.tar that is it. There is no such thing as dual extensions. i’m pretty sure boost::filesystem handles it that way. (split path, change_extension…) and its behavior is based on python if I’m not mistaken.

    – v.oddou

    Nov 26, 2013 at 7:29

566

Usually you already know the extension, so you might wish to use:

basename filename .extension

for example:

basename /path/to/dir/filename.txt .txt

and we get

filename

6

  • 88

    That second argument to basename is quite the eye-opener, ty kind sir/madam 🙂

    – akaIDIOT

    Jan 23, 2013 at 9:37


  • 13

    And how to extract the extension, using this technique? 😉 Oh, wait! We actually don’t know it upfront.

    Feb 13, 2014 at 11:48

  • 4

    Say you have a zipped directory that either ends with .zip or .ZIP. Is there a way you could do something like basename $file {.zip,.ZIP}?

    – Dennis

    Mar 30, 2014 at 20:56

  • 10

    While this only answers part of the OPs question, it does answer the question I typed into google. 🙂 Very slick!

    Nov 30, 2017 at 16:28

  • 1

    easy and POSIX compliant

    – gpanda

    Oct 12, 2018 at 15:36