Bash – String cropping in Bash

bashstring

Suppose I have a string in my variable as follows.

var="/fnxn/ngfdg/abc.ext"

I can get the root of this variable (remove extension .*) as follows

echo ${var%.*}

I can get the tail of this variable (removing the path */) as follows

echo ${var##*/}

But now I have to remove both the path and the extension. I can do it in two step using another variable as follows.

var2=${var##*/}
var3=${var2%.*}

But the single step option (similar to zsh syntax below) shows an error message "bad substitution" in bash.

echo ${${var##*/}%.*}

It would be useful if I get a single step comprehensible option to reduce the code length and avoid additional environment variable.

Best Answer

If you have a fixed extension, POSIX basename supports exactly the cropping you're attempting:

basename "$var" .ext

If you don't, zsh expansion supports exactly what you're trying:

If a ${...} type parameter expression or a $(...) type command substitution is used in place of name above, it is expanded first and the result is used as if it were the value of name. Thus it is possible to perform nested operations: ${${foo#head}%tail} substitutes the value of $foo with both ‘head’ and ‘tail’ deleted.

So in zsh

echo ${${var##*/}%.*}

will do what you expected.


If you're committed to Bash, and you want to save lines, you could use sed:

sed -e 's/\.[^.]*$//' <<<"${var##*/}"

That's just a regular-expression replacement of everything after the last . with nothing, after losing the prefix the same way you are now. I don't see this particularly saving memory over the two-line version, however, and it's probably less comprehensible too.

Note that there's no reason you can't use the same variable right through the process:

var2=${var##*/}
var2=${var2%.*}

The expansion happens before the assignment, so this is safe. If your concern is a really long filename being stored twice, that won't happen now, but I don't think that is a realistic problem.

Related Question