How do I get the file extension from bash? Here's what I tried:

filename=`basename $filepath`

By doing that I can get extension of bz2 from the path /dir/subdir/file.bz2, but I have a problem with the path /dir/subdir/file-1.0.tar.bz2.

I would prefer a solution using only bash without external programs if it is possible.

To make my question clear, I was creating a bash script to extract any given archive just by a single command of extract path_to_file. How to extract the file is determined by the script by seeing its compression or archiving type, that could be .tar.gz, .gz, .bz2 etc. I think this should involve string manipulation, for example if I get the extension .gz then I should check whether it has the string .tar before .gz — if so, the extension should be .tar.gz.

  • 2
    file="/dir/subdir/file-1.0.tar.bz2"; echo ${file##*.} prints '.bz2' here. What is the output that you're expecting? – axel_c Sep 4 '10 at 12:01
  • 1
    i need .tar.bz2 – uray Sep 4 '10 at 12:03
  • Related: Extract filename and extension in Bash. – kenorb Jun 29 '18 at 13:18

If the file name is file-1.0.tar.bz2, the extension is bz2. The method you're using to extract the extension (fileext=${filename##*.}) is perfectly valid¹.

How do you decide that you want the extension to be tar.bz2 and not bz2 or 0.tar.bz2? You need to answer this question first. Then you can figure out what shell command matches your specification.

  • One possible specification is that extensions must begin with a letter. This heuristic fails for a few common extensions like 7z, which might be best treated as a special case. Here's a bash/ksh/zsh implementation:

    basename=$filename; fileext=
    while [[ $basename = ?*.* &&
             ( ${basename##*.} = [A-Za-z]* || ${basename##*.} = 7z ) ]]

    For POSIX portability, you need to use a case statement for pattern matching.

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
    do …
  • Another possible specification is that some extensions denote encodings and indicate that further stripping is needed. Here's a bash/ksh/zsh implementation (requiring shopt -s extglob under bash and setopt ksh_glob under zsh):

    while [[ $basename = ?*.@(bz2|gz|lzma) ]]; do
    if [[ $basename = ?*.* ]]; then

    Note that this considers 0 to be an extension in file-1.0.gz.

¹ ${VARIABLE##SUFFIX} and related constructs are in POSIX, so they work in any non-antique Bourne-style shell such as ash, bash, ksh or zsh.

|improve this answer|||||
  • that should be solved, by checking if the string before last . token is archive type, for example tar, if its not archive type like 0 iteration should end. – uray Sep 4 '10 at 12:41
  • 2
    @uray: that works in this particular case, but it's not a general solution. Consider Maciej's example of .patch.lzma. A better heuristic would be to consider the string after the last .: if it's a compression suffix (.7z, .bz2, .gz, ...), continue stripping. – Gilles 'SO- stop being evil' Sep 4 '10 at 13:15
  • @NoamM What was wrong with the indentation? It's definitely broken after your edit: doubly-nested code is indented the same as singly-nested. – Gilles 'SO- stop being evil' Aug 28 '19 at 11:12

You might simplify matters by just doing pattern matching on the filename rather than extracting the extension twice:

case "$filename" in
    *.tar.bz2) bunzip_then_untar ;;
    *.bz2)     bunzip_only ;;
    *.tar.gz)  untar_with -z ;;
    *.tgz)     untar_with -z ;;
    *.gz)      gunzip_only ;;
    *.zip)     unzip ;;
    *.7z)      do something ;;
    *)         do nothing ;;
|improve this answer|||||
  • This solution is beautifully simple. – AsymLabs Oct 26 '16 at 9:48
$ echo "thisfile.txt"|awk -F . '{print $NF}'

Comments on this here: http://liquidat.wordpress.com/2007/09/29/short-tip-get-file-extension-in-shell-script/

|improve this answer|||||
  • 1
    not work for .tar.gz extension – uray Sep 4 '10 at 12:02
  • 4
    Well a .tar.gz is actually a tar inside a gzip file so it does work in the sense that it removes a gz extension from a gzip file. – Chris Sep 4 '10 at 15:24

Here's my shot at it: Translate dots to newlines, pipe through tail, get last line:

$> TEXT=123.234.345.456.456.567.678
$> echo $TEXT | tr . \\n | tail -n1
|improve this answer|||||
echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}

For example:

% echo $filename
% echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}
|improve this answer|||||
  • Does not work for all cases. Try with 'foo.7z' – axel_c Sep 4 '10 at 12:19
  • You need quotes, and better use printf in case the file name contains a backslash or begins with -: "${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}" – Gilles 'SO- stop being evil' Sep 4 '10 at 12:20
  • @axel_c: right, and I've implemented the same specification as Maciej as an example. What heuristic do you suggest that's better than “begins with a letter”? – Gilles 'SO- stop being evil' Sep 4 '10 at 12:22
  • 1
    @Gilles: i just think there's not a solution unless you use a precomputed list of known extensions, because an extension can be anything. – axel_c Sep 4 '10 at 12:42

One day I've created those tricky functions:

# args: string how_many
function get_last_letters(){ echo ${1:${#1}-$2:$2}; }
function cut_last_letters(){ echo ${1:0:${#1}-$2}; }

I've found this straightforward approach, very useful in many cases, not only when it goes about extensions.

For checking extensions - It's simple and reliable

~$ get_last_letters file.bz2 4
~$ get_last_letters file.0.tar.bz2 4

For cutting-off extension:

~$ cut_last_letters file.0.tar.bz2 4

For changing extension:

~$ echo $(cut_last_letters file.0.tar.bz2 4).gz

Or, if you like "handy functions:

~$ function cut_last_letters_and_add(){ echo ${1:0:${#1}-$2}"$3"; }
~$ cut_last_letters_and_add file.0.tar.bz2 4 .gz

P.S. If you liked those functions or found them usedfull, please refer to this post :) (and hopefully put a comment).

|improve this answer|||||

the jackman case-based answer is pretty good and portable, but if you just want the filename and extension in a variable i've found this solution:

INPUTFILEEXT=$( echo -n "$INPUTFILE" | rev | cut -d'.' -f1 | rev )
INPUTFILEEXT=$( echo -n $INPUTFILEEXT | tr '[A-Z]' '[a-z]' ) # force lowercase extension
INPUTFILENAME="`echo -n \"$INPUTFILE\" | rev | cut -d'.' -f2- | rev`"

# fix for files with multiple extensions like "gbamidi-v1.0.tar.gz"
INPUTFILEEXT2=$( echo -n "$INPUTFILENAME" | rev | cut -d'.' -f1 | rev )
if [ "$INPUTFILEEXT2" = "tar" ]; then
    # concatenate the extension
    # update the filename
    INPUTFILENAME="`echo -n \"$INPUTFILENAME\" | rev | cut -d'.' -f2- | rev`"

It only works with double extensions and the first one must be "tar".

But you can change the "tar" test line with a string length test and repeat the fix multiple times.

|improve this answer|||||

i solved it using this:

filename=`basename $filepath`
if [ "$fileext3" == "tar" ]; then

but this only work for known archiving type, in this case only tar

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.