Sign up ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

However it may be impossible, I hope I'm just bad man reader =)

Is there any way to substitute text in variables on several patterns at time or even using back reference?

For example, I have FILE=filename.ext and I want to change it to filename_sometext.ext. But I don't know that file extension is .ext. All I know about it is that extension is after last dot. So I can do it in two steps:


Can I do it on one step (smth like ${FILE/.\(*\)/_sometext.\1} [that doesn't work])?

By the way I need to do it in pure shell without sed/awk/etc. My shell is ksh, but if there is way to do it with bashisms I'd like to know it too.

share|improve this question
I assume you mean ksh88 not ksh93? – Mikel May 11 '12 at 8:55
What if the file name contains multiple dots? – Mikel May 11 '12 at 9:13
I mean ksh93. Does it matter so much? And extension is after last dot, it doesn't matter if multiple dots appear before. – rush May 11 '12 at 9:49
Yes, it matters, ksh88 is what most people mean when they say ksh, and it doesn't support many features that are specific to ksh93. Hence why I asked. ;-) – Mikel May 11 '12 at 15:20

4 Answers 4

up vote 5 down vote accepted

Bash Parameter Expansion says that the variable (FILE in your example) must be a parameter name. So they don't nest. And the last part of ${param/pattern/replacement} must be a string. So back references aren't supported.

My only advice is to use


to avoid adding a trailing dot if the file has no extension.


Apparently back references are supported in ksh93.

So you could use something like

share|improve this answer
I got information about back reference and nested substitution. Thank you. – rush May 11 '12 at 9:51
Your ${FILE/@(.*)/_something\1} would be like ${FILE/./_something.} and change the first occurrence of .. You'd need ${FILE/%.*([^.])/_something\0} or ${FILE/@(*)./\1_something.} to match on the last one like ${FILE%.*} would. – Stéphane Chazelas Oct 19 at 8:52

In this particular case I think FILE=${FILE%.*}_sometext.${FILE##*.} would do the job.

share|improve this answer
Yeah, it did the job, but I think my example was a little wrong for that. – rush May 11 '12 at 9:47


In any shell, you could concatenate variables or values:

$ a=One; b=Two; c=Three
$ d="$a$b$c"; echo $d

The ${parameter%word} type of structures is POSIX have been supported in most shells for a long time.
Assuming FILE=filename.ext, you could do:

$ FILE="${FILE%.*}_sometext.${FILE##*.}"; echo "$FILE"

That is one line (as requested) and works on most shells.

It is also possible to use variables (even with several dots):

echo "EXT=$EXT"
echo "final FILE=${FILE%.*}${ADDTEXT}.$EXT"

Or, in just one line:

echo "one   FILE=$ONEFILE"

Pattern substitution

What gets tricky is trying to use ${parameter/pattern/string} «Pattern substitution.»

A variable works in many shells (even busybox ash):

echo "ash   FILE=${FILE/.*/${NEWTEXT}}"

But not in older shells.

Only for FILE=filename.ext:
This works in bash (and ksh, ksh93, mksh, zsh) but not in ash, dash, sh or csh

echo "bash  FILE=${FILE/%.*/${ADDTEXT}.${FILE##*.}}"

Note that it is using the % character to indicate a match at the end of the parameter (but, sadly it is greedy and eats all dots in


The best method to control how greedy is the substitution is to use separate variables (as opposed to some from of «Pattern substitution.»)

share|improve this answer

If the shell variable $FILE contains a . the above command will reassign to it the old value less everything following the last . then _sometext and last the old value's last . and everything after.

And so:
printf %s\\n "$FILE"

If the shell variable $FILE does not contain a dot, then the _sometext string is merely appended to the end of the old value:

printf %s\\n "$FILE"


This is importantly different behavior than you might get if you did not nest the parameter expansion as I do on the end there:

printf %s\\n "$FILE"


When you nest the parameter expansion it is evaluated inside to outside, and so the first thing that happens is:

for    FILE in no_dots_at_all
do     printf %s\\n '${FILE#'"${FILE%.*}"'}'


...the shell removes any matched bits and uses the remains as the pattern to strip from the outer expansion. And so when there aren't any matched bits and the variable expands in full the outer expansion strips away all of the variable rather than substituting it into your expression twice.

share|improve this answer

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.