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

358,071

Solution 1

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 2

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 3

If you know the extension, you can use basename

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

Solution 4

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

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.

Share:
358,071

Related videos on Youtube

mimicocotopus
Author by

mimicocotopus

Updated on February 15, 2022

Comments

  • mimicocotopus
    mimicocotopus about 2 years

    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.

    • mpe
      mpe over 11 years
      basename $filename .exe would do the same thing. That's assuming you always know what extension you want to remove.
    • Charles Duffy
      Charles Duffy almost 8 years
      @mpe, you mean basename "$filename" .exe. Otherwise filenames with spaces would be bad news.
  • mimicocotopus
    mimicocotopus over 11 years
    Thanks. I also noticed that I need to use the echo command. name=echo $filename | cut -f1 -d'.'
  • jordanm
    jordanm over 11 years
    Backticks are deprecated by POSIX, $() is preferred.
  • Jens
    Jens over 8 years
    Forking and piping to get at a few characters is about the worst solution imaginable.
  • Scott Stensland
    Scott Stensland about 8 years
    The problem with this answer is it assumes input string has ONLY one dot ... @chepner below has a much better solution ... name=${filename%.*}
  • Carlos Robles
    Carlos Robles almost 8 years
    here the explanation of the command: gnu.org/software/bash/manual/html_node/…
  • user208145
    user208145 over 7 years
    And here I was about to use echo -n "This.File.Has.Periods.In.It.txt" | awk -F. '{$NF=""; print $0}' | tr ' ' '.' | rev | cut -c 2- | rev. Thanks.
  • supreme Pooba
    supreme Pooba over 7 years
    I forgot the middle rev, but once I saw it, this was great!
  • neric
    neric about 7 years
    This answer is characteristic of beginners and should not be spreaded. Use the builtin mechanism as described by chepner's answer
  • Hawker65
    Hawker65 about 6 years
    Does this work with files with multiple extensions like image.png.gz?
  • chepner
    chepner about 6 years
    %.* will only remove the last extension; if you want to remove all the extensions, use %%.*.
  • gniourf_gniourf
    gniourf_gniourf about 6 years
    How is this different from the answer given by Steven Penny 3 years ago?
  • Dale C. Anderson
    Dale C. Anderson over 5 years
    This seems to work way better than the accepted answer. It only shaves off the last extension if the file has more than one dot, whereas cut removes everything after the first dot.
  • Frotz
    Frotz over 5 years
    This one strips "tar.gz" by selecting characters before the first instance of a dot in the filename not counting the path. One probably doesn't want to strip extensions that way.
  • Gabriel Staples
    Gabriel Staples almost 5 years
    How can I remove just .wiki and end up with /home/jsmith/base?
  • Gabriel Staples
    Gabriel Staples almost 5 years
    Update: answer: See stackoverflow.com/a/32584935/4561887. Works perfectly!
  • jpmc26
    jpmc26 over 4 years
    Be warned this deletes the entire name if there is no extension and the path is relative. E.g., filename=./ihavenoextension.
  • jpmc26
    jpmc26 over 4 years
    Fails on ./ihavenoextension, just like the other answer.
  • Y00
    Y00 over 4 years
    be really careful if you want to use more than one dot like ./text.log
  • nassim
    nassim about 4 years
    Thanks I was looking for some time for this. I used it to trim the last octet of an IP address echo 192.168.5.123 | cut -f -3 -d '.'
  • Hibou57
    Hibou57 about 4 years
    It’s even better with an -s option given to cut, so that it returns an empty string whenever the filename contains no dot.
  • Tim Krief
    Tim Krief almost 4 years
    This should be the accepted answer in my opinion, since it works on path with dots in them, with hidden files starting with a dot, or even with file with multiple extensions.
  • FedKad
    FedKad over 3 years
    What happens when file1=/tmp.d/mainonetwosh? The sed expression should be replaced with 's/\.[^./]*$//'
  • FedKad
    FedKad over 3 years
    What happens with a file like this: file=/tmp.d/foo ?
  • FedKad
    FedKad over 3 years
    What happens with a file like this: filename=/tmp.d/foo?
  • Manish Singh
    Manish Singh over 3 years
    @FedonKadifeli, this works for just the file name, not for full path. But you can get the filename first using a similar approach by using / as the delimiter.
  • FedKad
    FedKad over 3 years
    It has problems with "dot files" also. Try with .profile for example.
  • mwfearnley
    mwfearnley over 3 years
    @Hawker65 if you know the file will have a .png.gz extension, you can use {file%.png.gz}. That will prevent it trimming after periods earlier in the file - or in the path!
  • mwfearnley
    mwfearnley over 3 years
    If you know it ends in .tar.gz, you can just use $(basename "$a" .tar.gz). Also, make sure to wrap your variable in quotes everywhere if there's any chance it will contain spaces or other weird characters!
  • mwfearnley
    mwfearnley over 3 years
    @gniourf_gniourf upvoting because you've made it a useful answer by showing how to correctly use it with a variable.
  • Jinyu
    Jinyu over 3 years
    it doesn't work if filename is "a.b.c.txt'
  • shaheen g
    shaheen g about 3 years
    This covers all the possibilities: stackoverflow.com/a/66177670/12969125
  • shaheen g
    shaheen g about 3 years
    This covers all the possibilities: stackoverflow.com/a/66177670/12969125
  • donvercety
    donvercety about 3 years
    @FedonKadifeli use something like this: basename "$INPUT" | rev | cut -f 2- -d '.' | rev works perfectly. $INPUT is the filename or the path.
  • virtualdj
    virtualdj over 2 years
    Thanks, the only answer that works with ./ihavenoextension!
  • Nam G VU
    Nam G VU over 2 years
    this should be the accepted answer in my view
  • rivy
    rivy over 2 years
    @chepner, @mwfearnley, To flexibly handle dot-files, an improvement would be: (1) remove last extension => echo "$(s=${filename:1}; echo -n ${filename:0:1}; echo ${s%.*})" ; (2) remove all extensions => echo "$(s=${filename:1}; echo -n ${filename:0:1}; echo ${s%%.*})".
  • jena
    jena over 2 years
    @GabrielStaples you could combine it with dirname, e.g. file=/home/jsmith/base.wiki; echo $(dirname $file)/$(basename $file .wiki)
  • JL Peyret
    JL Peyret over 2 years
    I went with this. The nice thing is that basename is made for this and I discovered a new capability in what it can do.
  • Smeterlink
    Smeterlink over 2 years
    The best answer so far!
  • Ravi OpenSource
    Ravi OpenSource over 2 years
    this worked for me