17

I have files in format below:

abc_asdfjhdsf_dfksfj_12345678.csv
hjjhk_hkjh_asd_asd_sd_98765498.csv
hgh_nn_25342134.exe

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

6 Answers 6

20

You could use awk also,

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

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).

5
  • Why two awk commands? Just set the field seperator to underscore or dot and print the column no 4. Jun 18, 2014 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, 2014 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. Jun 18, 2014 at 9:54
  • edited your answer. Jun 18, 2014 at 11:48
  • If I have the same question, but I have thousands of files, how do I read in each file sequentially and have the portion of the filename extracted? Do I use a loop? If so, how?
    – Andor Kiss
    Jan 3, 2022 at 19:34
14

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

file=abc_asdfjhdsf_dfksfj_12345678.csv
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'
3

You can use GNU grep:

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

Explanation

  • (?<=) 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 .

6
  • Does the dot represents a literal dot? Jun 18, 2014 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. Jun 18, 2014 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, 2014 at 9:54
  • @stephane yep. That's why I commented. Jun 18, 2014 at 9:57
  • If it has to be after the last _ and before the last ., that would rather be grep -Po '.*_\K.*(?=\.)' Jun 18, 2014 at 10:36
2

Simply:

a=hjjhk_hkjh_asd_asd_sd_98765498.csv
pos1=${a%_*}
pos2=${a%.*}
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

1

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}'
12345678

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

echo "hgh_nn_25342134.exe" | awk -F"." '{print $1}' | awk -F"_" '{print $NF}'
25342134
1
  • 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, 2014 at 16:37
1

Since the underscore to select is the last one:

Using a variable to contain the file name:

file=abc_asdfjhdsf_dfksfj_12345678.csv
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}

You must log in to answer this question.

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