## Extract filename and extension in Bash

### Question

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?

2018/07/10
1
2163
7/10/2018 3:04:27 PM

First, get file name without the path:

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


Alternatively, you can focus on the last '/' 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 : 2018/11/11 3582 11/11/2018 5:41:47 AM ~% 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. 2019/12/05 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  2011/10/19 You can use the magic of POSIX parameter expansion: bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$echo "${FILENAME%%.*}"
somefile
bash-3.2$echo "${FILENAME%.*}"
somefile.tar


There's a caveat in that if your filename was of the form ./somefile.tar.gz then echo ${FILENAME%%.*} would greedily remove the longest match to the . and you'd have the empty string. (You can work around that with a temporary variable: FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/} echo${FILENAME%%.*}


)

This site explains more.

${variable%pattern} Trim the shortest match from the end${variable##pattern}
Trim the longest match from the beginning
${variable%%pattern} Trim the longest match from the end${variable#pattern}
Trim the shortest match from the beginning

2020/01/10

That doesn't seem to work if the file has no extension, or no filename. Here is what I'm using; it only uses builtins and handles more (but not all) pathological filenames.

#!/bin/bash
for fullpath in "[email protected]"
do
filename="${fullpath##*/}" # Strip longest match of */ from start dir="${fullpath:0:${#fullpath} -${#filename}}" # Substring from 0 thru pos of filename
base="${filename%.[^.]*}" # Strip shortest match of . plus at least one non-dot char from end ext="${filename:${#base} + 1}" # Substring from len of base thru end if [[ -z "$base" && -n "$ext" ]]; then # If we have an extension and no base, it's really the base base=".$ext"
ext=""
fi

echo -e "$fullpath:\n\tdir = \"$dir\"\n\tbase = \"$base\"\n\text = \"$ext\""
done


And here are some testcases:

$basename-and-extension.sh / /home/me/ /home/me/file /home/me/file.tar /home/me/file.tar.gz /home/me/.hidden /home/me/.hidden.tar /home/me/.. . /: dir = "/" base = "" ext = "" /home/me/: dir = "/home/me/" base = "" ext = "" /home/me/file: dir = "/home/me/" base = "file" ext = "" /home/me/file.tar: dir = "/home/me/" base = "file" ext = "tar" /home/me/file.tar.gz: dir = "/home/me/" base = "file.tar" ext = "gz" /home/me/.hidden: dir = "/home/me/" base = ".hidden" ext = "" /home/me/.hidden.tar: dir = "/home/me/" base = ".hidden" ext = "tar" /home/me/..: dir = "/home/me/" base = ".." ext = "" .: dir = "" base = "." ext = ""  2009/09/10 You can use basename. Example: $ basename foo-bar.tar.gz .tar.gz
foo-bar


You do need to provide basename with the extension that shall be removed, however if you are always executing tar with -z then you know the extension will be .tar.gz.

This should do what you want:

tar -zxvf $1 cd$(basename \$1 .tar.gz)

2013/02/05