Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Example: I want to create a quick command to shrink images (for my filebrowser).

gm convert "$FN" -resize 50% "$(dirname $FN)/$(basename $FN .jpg/png/gif).jpg"

How can I specify that more than one suffix should be stripped?

share|improve this question
gm convert "$FN" -resize 50% "${FN%.*}.jpg" – Stéphane Chazelas May 8 '14 at 11:27
I think that’s where UNIX fails, that every program has to implement its own pattern matching and you can never be sure which does. – Profpatsch May 8 '14 at 11:52
What you call fail is just the nature of how tools were developed. Each developer of their respective tools can do however they see fit. Many of the tools do leverage a standard regex set of libraries and the nature of the chaining of commands via pipes can mitigate what you're calling a "fail". People need to keep in mind that there is no formal steering committee for how Unix works. – slm May 8 '14 at 17:06
up vote 12 down vote accepted

basename only takes one suffix to remove, and gives the base name (removes the directory components) which you don't want there anyway, so basename is not really the right tool for your need.

The traditional tool to extract data from a string is expr:

FN_without_extension=$(expr "x$FN" : 'x\(.*\)\.')

But modern shells (like zsh, bash, ksh, ash, yash, all POSIX compliant sh...) have builtin operators for that, so you hardly ever need expr nowadays (and it's best avoided as it's got a few issues).


removes the (smallest) part matching pattern from the end of $var

gm convert "$FN" -resize 50% "${FN%.*}.jpg"

Shells like tcsh or zsh have operators to remove extensions. Zsh:

gm convert $FN -resize 50% $FN:r.jpg

(r for rest).

If you want to remove the extension, only if it's one of jpg/png/gif, then that becomes more complicated and shell dependant.

With zsh:

gm convert $FN -resize 50% ${FN%.(jpg|png|gif)}.jpg

With ksh:

gm convert "$FN" -resize 50% "${FN%.@(jpg|png|gif)}.jpg"

With bash:

shopt -s extglob
gm convert "$FN" -resize 50% "${FN%.@(jpg|png|gif)}.jpg"

With expr:

gm convert "$FN" -resize 50% "$(
  expr \( "x$FN" : '\(.*\)\.png$' \| \
          "x$FN" : '\(.*\)\.jpg$' \| \
          "x$FN" : '\(.*\)\.gif$' \| "x$FN" \) : 'x\(.*\)')".jpg

(yes, that's convoluted, and that's to work around some of the issues of expr).

With some expr implementations, it can be simplified to:

expr \( "x$FN" : '\(.*\)\.\(png\|jpg\|gif\)$' \| "x$FN" \) : 'x\(.*\)'

You could also use sed:

FN_without_ext=$(printf '%s\n' "$FN" |
  sed -e '$!b' -e 's/\.png$//;t' -e 's/\.gif$//;t' -e 's/\.jpg$//')

If you want it case insensitive, you can replace the png/gif/jpg in all those solutions above with [pP][nN][gG]..., some shells/tools can also do case insensitive matching:


 setopt extendedglob



GNU sed:

 FN_without_ext=$(printf '%s\n' "$FN" | sed -r '$s/\.(png|gif|jpg)$//I')
share|improve this answer
Shell dependency is a bummer. But it can’t be helped I guess. – Profpatsch May 8 '14 at 13:06
+1 for "${FN%.*}.jpg". – MvG May 8 '14 at 13:40
Generally +1 for ${var%pattern}. I learned something new today. – running.t Oct 22 '15 at 11:49

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.