Question: I'm trying to set a variable in an sh script to the last 3 characters of the base name of a file (by base name I mean without the path *and* without the suffix). I've succeeded in doing this but, purely out of curiosity, I'm wondering if there is a shorter, single command I can use. Originally I had a one-liner with `awk`, but it was rather long. Currently I have this two-line script (assuming a complete filename is in `$1`):

```
filebase=`basename "$1"`
lastpart=`echo -n ${filebase%.*} | tail -c3`

```

So for example, *"/path/to/somefile.txt"* ends up with *"ile"* in `$lastpart`.

Can I somehow combine `basename` and the bit to strip the suffix into a single command, and is there a way to send it to `tail` (or something else I can use) without using a pipe? The suffix is unknown so I can't base it as a parameter to `basename`.

The main goal isn't actually so much to be as short as possible, as to be as readable at a glance as possible. The actual context of all of this is [this question on Superuser](https://superuser.com/questions/772181/matching-files-to-folders/772190#772190), where I'm trying to come up with a reasonably simple answer.

Answer: 
```
var=123456
echo "${var#"${var%???}"}"

###OUTPUT###

456

```

That first removes the last three characters from `$var` then removes from `$var` the results of that removal - which returns the last three characters of `$var`. Here are some examples more specifically aimed at demonstrating how you might do such a thing:

```
touch file.txt
path=${PWD}/file.txt
echo "$path"

/tmp/file.txt

base=${path##*/}
exten=${base#"${base%???}"}
base=${base%."$exten"}
{ 
    echo "$base" 
    echo "$exten" 
    echo "${base}.${exten}" 
    echo "$path"
}

file
txt
file.txt
/tmp/file.txt

```

You don't have to spread this all out through so many commands. You can compact this:

```
{
    base=${path##*/} exten= 
    printf %s\\n "${base%.*}" "${exten:=${base#"${base%???}"}}" "$base" "$path"
    echo "$exten"
}

file 
txt 
file.txt 
/tmp/file.txt
txt

```

Combining `$IFS` with `set`ting shell parameters can also be a very effective means of parsing and drilling through shell variables:

```
(IFS=. ; set -f; set -- ${path##*/}; printf %s "${1#"${1%???}"}")

```

That will get you only the three characters immediately preceding the first period following the last `/` in `$path`. If you want to retrieve only the first three characters immediately preceding the last `.` in `$path` *(for instance, if there is a possibility of more than one `.` in filename)*:

```
(IFS=.; set -f; set -- ${path##*/}; ${3+shift $(($#-2))}; printf %s "${1#"${1%???}"}")

```

In both cases you can do:

```
newvar=$(IFS...)

```

And...

```
(IFS...;printf %s "$2")

```

...will print what follows the `.`

If you don't mind using an external program you can do:

```
printf %s "${path##*/}" | sed 's/.*\(...\)\..*/\1/'

```

If there is a chance of a `\n`ewline character in the filename *(not applicable for the native shell solutions - they all handle that anyway)*:

```
printf %s "${path##*/}" | sed 'H;$!d;g;s/.*\(...\)\..*/\1/'

```
If perl is available, I find it can be more readable than other solutions, specifically because its regex language is more expressive and it has the `/x` modifier, which allows for writing clearer regexs: 

```
perl -e 'print $1 if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"

```

This prints nothing if there is no such match (if the basename has no extension or if the root before the extension is too short). Depending on your requirements, you can adjust the regex. This regex enforces the constraints:

1. It matches the 3 characters before the final extension (the part after and including the last dot). These 3 characters can contain a dot.
2. The extension can be empty (except for the dot).
3. The matched part and the extension must be part of the basename (the part after the last slash).

Using this in a command substitution has the normal issues with removing too many trailing newlines, a problem which also affects Stéphane's answer. It can be dealt with in both cases, but is a little easier here:

```
lastpart=$(
  perl -e 'print "$1x" if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
)
lastpart=${lastpart%x}  # allow for possible trailing newline

```