How can I remove the extension of a filename in a shell script?

BashShellShCutGnu Coreutils

Bash Problem Overview


What's wrong with the following code?

name='$filename | cut -f1 -d'.''

As is, I get the literal string $filename | cut -f1 -d'.', but if I remove the quotes I don't get anything. Meanwhile, typing

"test.exe" | cut -f1 -d'.'

in a shell gives me the output I want, test. I already know $filename has been assigned the right value. What I want to do is assign to a variable the filename without the extension.

Bash Solutions


Solution 1 - Bash

You can also use parameter expansion:

$ filename=foo.txt
$ echo "${filename%.*}"
foo

Just be aware that if there is no file extension, it will look further back for dots, e.g.

  • If the filename only starts with a dot (e.g. .bashrc) it will remove the whole filename.
  • If there's a dot only in the path (e.g. path.to/myfile or ./myfile), then it will trim inside the path.

Solution 2 - Bash

You should be using the command substitution syntax $(command) when you want to execute a command in script/command.

So your line would be

name=$(echo "$filename" | cut -f 1 -d '.')

Code explanation:

  1. echo get the value of the variable $filename and send it to standard output
  2. We then grab the output and pipe it to the cut command
  3. The cut will use the . as delimiter (also known as separator) for cutting the string into segments and by -f we select which segment we want to have in output
  4. Then the $() command substitution will get the output and return its value
  5. The returned value will be assigned to the variable named name

Note that this gives the portion of the variable up to the first period .:

$ filename=hello.world
$ echo "$filename" | cut -f 1 -d '.'
hello
$ filename=hello.hello.hello
$ echo "$filename" | cut -f 1 -d '.'
hello
$ filename=hello
$ echo "$filename" | cut -f 1 -d '.'
hello

Solution 3 - Bash

If you know the extension, you can use basename

$ basename /home/jsmith/base.wiki .wiki
base

Solution 4 - Bash

If your filename contains a dot (other than the one of the extension) then use this:

echo $filename | rev | cut -f 2- -d '.' | rev

Solution 5 - Bash

file1=/tmp/main.one.two.sh
t=$(basename "$file1")                        # output is main.one.two.sh
name=$(echo "$file1" | sed -e 's/\.[^.]*$//') # output is /tmp/main.one.two
name=$(echo "$t" | sed -e 's/\.[^.]*$//')     # output is main.one.two

use whichever you want. Here I assume that last . (dot) followed by text is extension.

Solution 6 - Bash

Using POSIX's built-in only:

#!/usr/bin/env sh
path=this.path/with.dots/in.path.name/filename.tar.gz

# Get the basedir without external command
# by stripping out shortest trailing match of / followed by anything
dirname=${path%/*}

# Get the basename without external command
# by stripping out longest leading match of anything followed by /
basename=${path##*/}

# Strip uptmost trailing extension only
# by stripping out shortest trailing match of dot followed by anything
oneextless=${basename%.*}; echo "$oneextless" 

# Strip all extensions
# by stripping out longest trailing match of dot followed by anything
noext=${basename%%.*}; echo "$noext"

# Printout demo
printf %s\\n "$path" "$dirname" "$basename" "$oneextless" "$noext"

Printout demo:

this.path/with.dots/in.path.name/filename.tar.gz
this.path/with.dots/in.path.name
filename.tar.gz
filename.tar
filename

Solution 7 - Bash

#!/bin/bash
file=/tmp/foo.bar.gz
echo $file ${file%.*}

outputs:

/tmp/foo.bar.gz /tmp/foo.bar

Note that only the last extension is removed.

Solution 8 - Bash

My recommendation is to use basename.
It is by default in Ubuntu, visually simple code and deal with majority of cases.

Here are some sub-cases to deal with spaces and multi-dot/sub-extension:

pathfile="../space fld/space -file.tar.gz"
echo ${pathfile//+(*\/|.*)}

It usually get rid of extension from first ., but fail in our .. path

echo **"$(basename "${pathfile%.*}")"**  
space -file.tar     # I believe we needed exatly that

Here is an important note:

> I used double quotes inside double quotes to deal with spaces. Single quote will not pass due to texting the $. > Bash is unusual and reads "second "first" quotes" due to expansion.

However, you still need to think of .hidden_files

hidden="~/.bashrc"
echo "$(basename "${hidden%.*}")"  # will produce "~" !!!  

not the expected "" outcome. To make it happen use $HOME or /home/user_path/
because again bash is "unusual" and don't expand "~" (search for bash BashPitfalls)

hidden2="$HOME/.bashrc" ;  echo '$(basename "${pathfile%.*}")'

Solution 9 - Bash

Answers provided previously have problems with paths containing dots. Some examples:

/xyz.dir/file.ext
./file.ext
/a.b.c/x.ddd.txt

I prefer to use |sed -e 's/\.[^./]*$//'. For example:

$ echo "/xyz.dir/file.ext" | sed -e 's/\.[^./]*$//'
/xyz.dir/file
$ echo "./file.ext" | sed -e 's/\.[^./]*$//'
./file
$ echo "/a.b.c/x.ddd.txt" | sed -e 's/\.[^./]*$//'
/a.b.c/x.ddd

Note: If you want to remove multiple extensions (as in the last example), use |sed -e 's/\.[^/]*$//':

$ echo "/a.b.c/x.ddd.txt" | sed -e 's/\.[^/]*$//'
/a.b.c/x

However, this method will fail in "dot-files" with no extension:

$ echo "/a.b.c/.profile" | sed -e 's/\.[^./]*$//'
/a.b.c/

To cover also such cases, you can use:

$ echo "/a.b.c/.profile" | sed -re 's/(^.*[^/])\.[^./]*$/\1/'
/a.b.c/.profile

Solution 10 - Bash

#!/bin/bash
filename=program.c
name=$(basename "$filename" .c)
echo "$name"

outputs:

program

Solution 11 - Bash

As pointed out by Hawker65 in the comment of chepner answer, the most voted solution does neither take care of multiple extensions (such as filename.tar.gz), nor of dots in the rest of the path (such as this.path/with.dots/in.path.name). A possible solution is:

a=this.path/with.dots/in.path.name/filename.tar.gz
echo $(dirname $a)/$(basename $a | cut -d. -f1)

Solution 12 - Bash

This one covers all possibilities! (dot in the path or not; with extension or no extension):

tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*});echo $filename_noextension

Notes:

  • It gives you the filename without any extension. So there is no path in the $filename_noextension variable.
  • You end up with two unwanted variables $tmp1 and $tmp2. Make sure you are not using them in your script.

examples to test:

filename=.bashrc; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=.bashrc.txt; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=.bashrc.txt.tar; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=~/.bashrc; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=~/.bashrc.txt.tar; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=bashrc; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=bashrc.txt; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=bashrc.txt.tar; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=~/bashrc; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=~/bashrc.txt.tar; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

Solution 13 - Bash

In Zsh:

fullname=bridge.zip
echo ${fullname:r}

It's simple, clean and it can be chained to remove more than one extension:

fullname=bridge.tar.gz
echo ${fullname:r:r}

And it can be combined with other similar modifiers.

Solution 14 - Bash

Two problems with your code:

  1. You used a ' (tick) instead of a ` (back tick) to surround the commands that generate the string you want to store in the variable.
  2. You didn't "echo" the variable "$filename" to the pipe into the "cut" command.

I'd change your code to "name=`echo $filename | cut -f 1 -d '.' `", as shown below (again, notice the back ticks surrounding the name variable definition):

$> filename=foo.txt
$> echo $filename
foo.txt
$> name=`echo $filename | cut -f1 -d'.'`
$> echo $name
foo
$> 

Solution 15 - Bash

enter image description here

Assuming your files have .new extension

ls -1 | awk '{ print "mv "$1" `basename "$1" .new`"}' | sh

Since it is not showing special quotes after posting, please see image.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmimicocotopusView Question on Stackoverflow
Solution 1 - BashchepnerView Answer on Stackoverflow
Solution 2 - BashRohanView Answer on Stackoverflow
Solution 3 - BashZomboView Answer on Stackoverflow
Solution 4 - Bashmanish_sView Answer on Stackoverflow
Solution 5 - BashRaghwendraView Answer on Stackoverflow
Solution 6 - BashLéa GrisView Answer on Stackoverflow
Solution 7 - BashC. Paul BondView Answer on Stackoverflow
Solution 8 - BashAlexey K.View Answer on Stackoverflow
Solution 9 - BashFedKadView Answer on Stackoverflow
Solution 10 - BashboobooView Answer on Stackoverflow
Solution 11 - BashMadMageView Answer on Stackoverflow
Solution 12 - Bashshaheen gView Answer on Stackoverflow
Solution 13 - BashbeemteeView Answer on Stackoverflow
Solution 14 - BashFanDeLaUView Answer on Stackoverflow
Solution 15 - BashAvadh JoshiView Answer on Stackoverflow