3

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.

4
  • 4
    You can't in any modern Bourne-like shell but zsh, which allow you to do nested parameter substitution.
    – cuonglm
    Aug 29, 2016 at 7:43
  • I am not focusing on "nested parameter substitution", but cropping head and tail of a string in single step.
    – gibies
    Aug 29, 2016 at 7:51
  • 1
    "it can significantly reduce the code length and memory usage" [citation needed] (if you're worried about the number of environment variables, you can re-use them: var2=${var##*/}; var2=${var2%.*}). Aug 29, 2016 at 8:06
  • I withdraw the statement.
    – gibies
    Aug 29, 2016 at 8:11

1 Answer 1

5

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.

12
  • Yes, I prefer BASH.
    – gibies
    Aug 29, 2016 at 8:22
  • If you want to save lines, just write both substitutions on one line, it's still shorter than the sed (and more readable perhaps)...
    – ilkkachu
    Aug 29, 2016 at 8:28
  • 1
    In zsh, $var:t:r (like in csh) would be enough. Aug 29, 2016 at 8:36
  • 2
    @gibies, what I meant is that echo ${var%.*} is zsh syntax. In bash, you need: echo "${var%.*}". (well, strictly speaking, that would be printf '%s\n' "${var%.*}" (in zsh as well)). Aug 29, 2016 at 9:08
  • 2
    @gibies, omitting the quotes is invoking the split+glob operator in bash and most other Bourne-like shells. Curly brackets are generally only cosmetic, the quotes are necessary. See mywiki.wooledge.org/BashPitfalls, or any of these Q&A here: 1, 2, 3, 4, 5... Try for instance with var='foo/bar * baz' with the default value of $IFS. Compare with zsh. Aug 29, 2016 at 11:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .