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

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.

This question is related to bash shell sh cut gnu-coreutils

The answer is


#!/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.


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

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"


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
$> 

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

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

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.

If you know the extension, you can use basename

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

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

outputs:

program

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)

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.


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 "$noext" 

# 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

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%.*}")'

Examples related to bash

Comparing a variable with a string python not working when redirecting from bash script Zipping a file in bash fails How do I prevent Conda from activating the base environment by default? Get first line of a shell command's output Fixing a systemd service 203/EXEC failure (no such file or directory) /bin/sh: apt-get: not found VSCode Change Default Terminal Run bash command on jenkins pipeline How to check if the docker engine and a docker container are running? How to switch Python versions in Terminal?

Examples related to shell

Comparing a variable with a string python not working when redirecting from bash script Get first line of a shell command's output How to run shell script file using nodejs? Run bash command on jenkins pipeline Way to create multiline comments in Bash? How to do multiline shell script in Ansible How to check if a file exists in a shell script How to check if an environment variable exists and get its value? Curl to return http status code along with the response docker entrypoint running bash script gets "permission denied"

Examples related to sh

How to run a cron job inside a docker container? I just assigned a variable, but echo $variable shows something else How to run .sh on Windows Command Prompt? Shell Script: How to write a string to file and to stdout on console? How to cat <<EOF >> a file containing code? Assigning the output of a command to a variable What does set -e mean in a bash script? Get specific line from text file using just shell script Printing PDFs from Windows Command Line Ubuntu says "bash: ./program Permission denied"

Examples related to cut

Loop through a comma-separated shell variable cat, grep and cut - translated to python How to find the last field using 'cut' Using the grep and cut delimiter command (in bash shell scripting UNIX) - and kind of "reversing" it? Using cut command to remove multiple columns how to remove the first two columns in a file using shell (awk, sed, whatever) How can I remove the extension of a filename in a shell script? bash script use cut command at variable and store result at another variable How to specify more spaces for the delimiter using cut? Copy and paste content from one file to another file in vi

Examples related to gnu-coreutils

How can I remove the extension of a filename in a shell script? How do I read the source code of shell commands? How to extract the first two characters of a string in shell scripting?