I have files in format below:


I want to get the value before the . and after the last _.

The result would look like:

abc_asdfjhdsf_dfksfj_12345678.csv   ----> 12345678
hjjhk_hkjh_asd_asd_sd_98765498.csv  ----> 98765498
hgh_nn_25342134.exe                 ----> 25342134

You could use awk also,

$ echo "abc_asdfjhdsf_dfksfj_12345678.csv" | awk -F'[_.]' '{print $4}'

It sets the Field seperator as _ or .. Then printing the column number 4 will give you the desired result (you may also prefer $(NF-1) (the but-last field) instead of $4).

  • Why two awk commands? Just set the field seperator to underscore or dot and print the column no 4. – Avinash Raj Jun 18 '14 at 9:33
  • @AvinashRaj if we set FS as "_" field no 4 is "12345678.csv" which has .csv which is not required. If we set the FS as "." and print 4th field then it will print nothing , coz wrt FS "." there is only 2 fields, abc_asdfjhdsf_dfksfj_12345678 & csv. – Tingrammer Jun 18 '14 at 9:50
  • Its or. Add this to your command instead of your FS value. -Fcharacter class symbol inside that put underscore dot close the character class. That's all. Sorry commented through my mobile. – Avinash Raj Jun 18 '14 at 9:54
  • edited your answer. – Avinash Raj Jun 18 '14 at 11:48

If you have the file name in a POSIX shell variable:

n=${file%.*}   # n becomes abc_asdfjhdsf_dfksfj_12345678
n=${file##*_}  # n becomes 12345678.csv

By explanation:

  • ${variable%pattern} is like $variable, minus shortest matching pattern from the back-end;
  • ${variable##pattern} is like $variable, minus the longest matching pattern from front-end.

See a reference like this one for more on parameter expansion.

If the list of file names is on a text stream with one filename per line:

sed -n 's/.*_\(.*\)\..*/\1/p'

You can use GNU grep:

$ echo abc_asdfjhdsf_dfksfj_12345678.csv | grep -oP '(?<=_)\d+(?=\.)'


  • (?<=) is lookbehind, (?<=_) matches an underscore _ before pattern.
  • \d+ matches one or more number.
  • (?=) is lookahead, (?=\.) matches a dot . after pattern.

The whole regex means match all things between _ and .

  • Does the dot represents a literal dot? – Avinash Raj Jun 18 '14 at 9:31
  • @AvinashRaj, no, . matches any character there. So that code matches any sequence of digits following an underscore that is followed by at least one character, so it's wrong. On _12_23, it would output 12 and 2. – Stéphane Chazelas Jun 18 '14 at 9:53
  • @StéphaneChazelas: Oh, my mistake, fixed it. I used this for test case and not check the doc, serious wrong here. Thanks. – cuonglm Jun 18 '14 at 9:54
  • @stephane yep. That's why I commented. – Avinash Raj Jun 18 '14 at 9:57
  • If it has to be after the last _ and before the last ., that would rather be grep -Po '.*_\K.*(?=\.)' – Stéphane Chazelas Jun 18 '14 at 10:36

Since the underscore to select is the last one:

Using a variable to contain the file name:

n=${file%.*}          # remove the extension `.csv`
n=${n#"${n%_*}_"}     # remove up to the last underscore `_`
  1. First remove the extension (after the last dot)
  2. build a value removing from to the last _: "${n%_*}_"
  3. remove the value from 2 from the leading of n: ${n#value}


echo ${a:${#pos1}+1:${#pos2}-${#pos1}-1}

get the offset of last _ to pos1 get the offset of last . to pos2 substring from _ offset to . offset


you can get the same using awk

awk -F"." '{print $1}' | awk -F"_" '{print $NF}'

from your example

echo "abc_asdfjhdsf_dfksfj_12345678.csv" | awk -F"." '{print $1}' | awk -F"_" '{print $NF}'

echo "hjjhk_hkjh_asd_asd_sd_98765498.csv" | awk -F"." '{print $1}' | awk -F"_" '{print $NF}'

echo "hgh_nn_25342134.exe" | awk -F"." '{print $1}' | awk -F"_" '{print $NF}'
  • Hi upkar, welcome to unix.SE. You're answer is much more readable when you take advantage of Stack Exchange's formatting markup. I've edited your post to insert the markup. You can click edit yourself to see how the small changes I made make it much more clear. See the markup help for more information. – drs Jun 19 '14 at 16:37

protected by Community Feb 6 '18 at 23:13

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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