480

How would I get just the filename without the extension and no path?

The following gives me no extension, but I still have the path attached:

source_file_filename_no_ext=${source_file%.*}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Keith
  • 4,801
  • 2
  • 16
  • 4

6 Answers6

820

Many UNIX-like operating systems have a basename executable for a very similar purpose (and dirname for the path):

pax> full_name=/tmp/file.txt
pax> base_name=$(basename ${full_name})
pax> echo ${base_name}
file.txt

That unfortunately just gives you the file name, including the extension, so you'd need to find a way to strip that off as well.

So, given you have to do that anyway, you may as well find a method that can strip off the path and the extension.

One way to do that (and this is a bash-only solution, needing no other executables):

pax> full_name=/tmp/xx/file.tar.gz
pax> xpath=${full_name%/*} 
pax> xbase=${full_name##*/}
pax> xfext=${xbase##*.}
pax> xpref=${xbase%.*}
pax> echo "path='${xpath}', pref='${xpref}', ext='${xfext}'"

path='/tmp/xx', pref='file.tar', ext='gz'

That little snippet sets xpath (the file path), xpref (the file prefix, what you were specifically asking for) and xfext (the file extension).

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • I know there is something to do with bash like the above. I just don't know what the key word is. I would like to get get the pathname, filename, and extension separated into different variables. – Keith Jul 29 '10 at 13:31
  • If you want to get path use: path=$(echo $filename | sed -e 's/\/[^\/]*$/\//') If you want to get extension: ext=$(echo $filename | sed -e 's/[^\.]*\.//') – jcubic Jul 29 '10 at 13:38
  • 3
    @Keith: for pathname, use `path=$(dirname $filename)`; there isn't a command to give you the extension per se, but @paxdiablo showed you how the shell can do it. – Jonathan Leffler Sep 19 '10 at 00:36
  • @jcubic your method is the slowest. paxdiablo method is perfect and the fastest. – Zibri May 03 '13 at 11:21
  • 3
    You can read more about the bash-only solution in the [manual](http://linux.die.net/man/1/bash) under **parameter expansion**. – Iain Samuel McLean Elder Jan 13 '14 at 01:15
  • @paxdiablo why is there a `$` in front of `$(basename)` – Startec Aug 01 '16 at 01:11
  • 1
    @Startec, because that's how you run an executable (like `basname`), capture it's output, and use that output within a `bash` command as if you had typed it in explicitly. It's similar to using backticks but with greater ease of nesting where necessary. – paxdiablo Aug 01 '16 at 01:23
  • This doesn't answer the question. – mihai Dec 04 '16 at 14:35
  • @Mihai, which particular part of the question do you contend is not covered in my answer? It provides both the standard UNIXy "use-the-tools-provided" and bash-pure approaches to get the file name sans extension from the full path. If your complaint is that you believe `file.tar` is *not* the name without the extension, you should re-read. The actual file given was `file.tar.gz` of which I consider `gz` to be the extension. – paxdiablo Dec 05 '16 at 07:09
  • @paxdiablo, the "How would I get just the filename _without_ the extension and no path?" part?! – mihai Dec 05 '16 at 08:05
  • @Mihai: yes, which is what was delivered. Given something like `/x/y/z.txt`, the answer would be `z`. Both options in the answer give that. If you have a *different* idea of what it should be, please let me know. – paxdiablo Dec 05 '16 at 11:05
  • @paxdiablo, the first example, with the `basename` doesn't: `pax> echo $b`, `file.txt` (your comment from above is different from the result you're providing in your answer). I think you actually have to pass the extension of the file as an argument to `basename`, as in `$ basename file.txt .txt`. This will result in `file`. Is it not so? – mihai Dec 05 '16 at 11:12
  • @Mihai: okay, understand now, thank you. Have modified the answer, hopefully making it better. – paxdiablo Dec 05 '16 at 11:21
  • `pax> xpref=${xbase%.*}` extracts first occurrence of file name extention. If we require to extract multiple filename-extentions then this worked for me `pax> xpref=${xbase%.*$}` (note the `$` appended for string extraction till the end of line) – AjayKumarBasuthkar Aug 04 '17 at 02:39
  • What happen if I have a wildcard path like `user/my_folder/[this_is_my_file]*`? What I obtain when I follow these steps is `[this_is_my_file]*` – Henry Navarro Dec 05 '18 at 12:48
  • @Henry, you appear to have answered your own question :-) If you want different behaviour, you should probably ask another question. – paxdiablo Dec 05 '18 at 22:33
87

basename and dirname solutions are more convenient. Those are alternative commands:

FILE_PATH="/opt/datastores/sda2/test.old.img"
echo "$FILE_PATH" | sed "s/.*\///"

This returns test.old.img like basename.

This is salt filename without extension:

echo "$FILE_PATH" | sed -r "s/.+\/(.+)\..+/\1/"

It returns test.old.

And following statement gives the full path like dirname command.

echo "$FILE_PATH" | sed -r "s/(.+)\/.+/\1/"

It returns /opt/datastores/sda2

Fırat Küçük
  • 5,613
  • 2
  • 50
  • 53
  • cool, what if there are parameters ? – Pavel Niedoba Oct 06 '17 at 11:15
  • this doesn't work if your path is just `file.txt`. your second code snipper should be `sed -r "s/(.+\/)?(.+)\..+/\2/` instead – Cardin Feb 05 '22 at 13:21
  • I've tested @Cardin. seems it works. `FILE_PATH="file.txt"; echo "$FILE_PATH" | sed "s/.*\///"; echo "$FILE_PATH" | sed -r "s/.+\/(.+)\..+/\1/"; echo "$FILE_PATH" | sed -r "s/(.+)\/.+/\1/";` returns 3 times `file.txt` – Fırat Küçük Feb 07 '22 at 10:57
  • 1
    @FıratKÜÇÜK You are right, I must have confused it with something else. Sorry about that! – Cardin Feb 08 '22 at 02:56
27

Here is an easy way to get the file name from a path:

echo "$PATH" | rev | cut -d"/" -f1 | rev

To remove the extension you can use, assuming the file name has only ONE dot (the extension dot):

cut -d"." -f1
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ivry345
  • 295
  • 3
  • 2
  • 9
    That's not a good assumption, and there are tools and commands specificly designed to do this properly. – Tony Jun 04 '15 at 02:41
  • 2
    Additionally, I wouldn't recommend using the variable name `PATH`, since this can conflict with the system's `PATH` variable – TabeaKischka Nov 24 '16 at 18:44
22
$ file=${$(basename $file_path)%.*}
mihai
  • 4,592
  • 3
  • 29
  • 42
  • 11
    This returns "bad substitution" in bash v4.4.7. I think Fırat KÜÇÜK's sed solution is better, i.e. $(basename $the_file_path) | sed "s/\..*//" – Marshal May 29 '17 at 23:26
  • 3
    I meant `echo $(basename $the_file_path) | sed "s/\..*//"` – Marshal May 29 '17 at 23:44
  • 1
    Is it working with spaces and dots in the filename? If no, how to do it? – 16851556 Apr 14 '21 at 11:49
  • Remove the spaces and dots from your filenames. – mihai Apr 15 '21 at 04:43
  • 1
    For me this also gives "bad substitution" error in Bash 4.2.46. But it works if I replace order of operators, i.e. file=$(basename ${file_path%.*}) – nt86 Oct 21 '21 at 11:51
16

Some more alternative options because regexes (regi ?) are awesome!

Here is a Simple regex to do the job:

 regex="[^/]*$"

Example (grep):

 FP="/hello/world/my/file/path/hello_my_filename.log"
 echo $FP | grep -oP "$regex"
 #Or using standard input
 grep -oP "$regex" <<< $FP

Example (awk):

 echo $FP | awk '{match($1, "$regex",a)}END{print a[0]}
 #Or using stardard input
 awk '{match($1, "$regex",a)}END{print a[0]} <<< $FP

If you need a more complicated regex: For example your path is wrapped in a string.

 StrFP="my string is awesome file: /hello/world/my/file/path/hello_my_filename.log sweet path bro."

 #this regex matches a string not containing / and ends with a period 
 #then at least one word character 
 #so its useful if you have an extension

 regex="[^/]*\.\w{1,}"

 #usage
 grep -oP "$regex" <<< $StrFP

 #alternatively you can get a little more complicated and use lookarounds
 #this regex matches a part of a string that starts with /  that does not contain a / 
 ##then uses the lazy operator ? to match any character at any amount (as little as possible hence the lazy)
 ##that is followed by a space
 ##this allows use to match just a file name in a string with a file path if it has an exntension or not
 ##also if the path doesnt have file it will match the last directory in the file path 
 ##however this will break if the file path has a space in it.

 regex="(?<=/)[^/]*?(?=\s)"

 #to fix the above problem you can use sed to remove spaces from the file path only
 ## as a side note unfortunately sed has limited regex capibility and it must be written out in long hand.
 NewStrFP=$(echo $StrFP | sed 's:\(/[a-z]*\)\( \)\([a-z]*/\):\1\3:g')
 grep -oP "$regex" <<< $NewStrFP

Total solution with Regexes:

This function can give you the filename with or without extension of a linux filepath even if the filename has multiple "."s in it. It can also handle spaces in the filepath and if the file path is embedded or wrapped in a string.

#you may notice that the sed replace has gotten really crazy looking
#I just added all of the allowed characters in a linux file path
function Get-FileName(){
    local FileString="$1"
    local NoExtension="$2"
    local FileString=$(echo $FileString | sed 's:\(/[a-zA-Z0-9\<\>\|\\\:\)\(\&\;\,\?\*]*\)\( \)\([a-zA-Z0-9\<\>\|\\\:\)\(\&\;\,\?\*]*/\):\1\3:g')

    local regex="(?<=/)[^/]*?(?=\s)"

    local FileName=$(echo $FileString | grep -oP "$regex")

    if [[ "$NoExtension" != "" ]]; then
        sed 's:\.[^\.]*$::g' <<< $FileName
    else
        echo "$FileName"
    fi
}

## call the function with extension
Get-FileName "my string is awesome file: /hel lo/world/my/file test/path/hello_my_filename.log sweet path bro."

##call function without extension
Get-FileName "my string is awesome file: /hel lo/world/my/file test/path/hello_my_filename.log sweet path bro." "1"

If you have to mess with a windows path you can start with this one:

 [^\\]*$       
jkdba
  • 2,378
  • 3
  • 23
  • 33
13
$ source_file_filename_no_ext=${source_file%.*}
$ echo ${source_file_filename_no_ext##*/}
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • I only get a wildcard with the extension using this. – Kebman Feb 12 '17 at 20:13
  • I prefer this, as it can be done in pure bash without running any other executable. However, there are two steps here where IMO there need only be one, just like the currently top voted answer at the marked duplicate: `filename="${fileNameWithPath##*/}"` – Alex Hall Aug 02 '20 at 15:48