22

Consider the following bash script:

#!/bin/bash
echo "${1##*.}" 

This script prints the extension of a file if the file name is supplied as the first command line argument, something like following:

$ ./script.sh ./file.pdf

In the above case pdf is printed.

Please explain how the expression ${1##*.} is able to extract the extension of the file.

(I understand what $0, $1, $2, $# do in bash and also understand about regular expressions to some extent)

blahdiblah
  • 115
  • 2
DUKE
  • 467
  • 4
  • 6
  • 10

3 Answers3

34

Bash parameter expansion supports several modifications it can do to the value while expanding a variable. One of them is ##, which removes the longest prefix of the value matching a pattern (patterns are not regular expressions here).

In this case the pattern is *.. That matches any zero or more characters followed by a .. ${x##*.} means to remove all of the string up to the last . character, and leave everything after that dot.

${1##*.} means to do that expansion using the value of the first positional parameter, the one you'd usually access with $1. The final result of

echo "${1##*.}"

is then to print out the part of the first argument of the script that comes after the last ., which is the filename extension.

If the pattern doesn't match at all, the full value of the variable is expanded, just as if you hadn't used the ##. In this case, if the argument you gave didn't have a . in it at all then you'd just get it back out again.

Bash also supports a single # to take the shortest matching prefix off, and the same thing with % to match the end of the string instead.

Michael Homer
  • 74,824
  • 17
  • 212
  • 233
  • 4
    Wonderful answer. Clear as a bell, and I have learned something new which should be helpful in my bash scripts. – Warwick Jul 31 '14 at 03:10
  • @mikeserv: Fair point on non-matching expansion (edited). This is explicitly a Bash question, though, so I'm not going to go all POSIX on it this time. – Michael Homer Jul 31 '14 at 04:10
  • and fair enough about not going all POSIXy. I just thought something like *`bash` supported parameter expansion* as opposed to *`bash` parameter expansion* would be nearer the mark. – mikeserv Jul 31 '14 at 17:44
9

Simple example:

$ A=my.file.name.txt

$ echo ${A}
my.file.name.txt

$ echo ${A#m}
y.file.name.txt

$ echo ${A#my}
.file.name.txt

$ echo ${A#*.}
file.name.txt

$ echo ${A##*.}
txt

In a script, ${1} is the first argument, and the same concept applies.

7

Note Bash parameter expansion :

You can trim a string from the head by using # operator

You can trim a string from the tail by using % operator

lets take an example :

[my->prompt]$ VAR="head:string:tail"
[my->prompt]$ echo ${VAR##*:}               //trim from the head -> till the last ':'
tail
[my->prompt]$ echo ${VAR#*:}                //trim from the head -> till the first ':' 
string:tail
[my->prompt]$ echo ${VAR%%:*}               //trim from the tail <- till the last ':'
head
[my->prompt]$ echo ${VAR%:*}                //trim from the tail <- till the first ':'
head:string
[my->prompt]$ VAR2=${VAR%:*}                //VAR2="head:string"
[my->prompt]$ echo $VAR2
head:string
[my->prompt]$ echo ${VAR2#*:}               //trim from the head -> till the first ':' 
string
DUKE
  • 467
  • 4
  • 6
  • 10
bachN
  • 191
  • 1