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
    You can't in any modern Bourne-like shell but zsh, which allow you to do nested parameter substitution. – cuonglm Aug 29 '16 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 '16 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%.*}). – Stephen Kitt Aug 29 '16 at 8:06
  • I withdraw the statement. – gibies Aug 29 '16 at 8:11
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.

  • Yes, I prefer BASH. – gibies Aug 29 '16 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 '16 at 8:28
  • 1
    In zsh, $var:t:r (like in csh) would be enough. – Stéphane Chazelas Aug 29 '16 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)). – Stéphane Chazelas Aug 29 '16 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. – Stéphane Chazelas Aug 29 '16 at 11:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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