181

I have written the following lines to get the last character of a string:

str=$1
i=$((${#str}-1))
echo ${str:$i:1}

It works for abcd/:

$ bash last_ch.sh abcd/
/

It does not work for abcd*:

$ bash last_ch.sh abcd*
array.sh assign.sh date.sh dict.sh full_path.sh last_ch.sh

It lists the files in the current folder.

Mateusz Piotrowski
  • 8,029
  • 10
  • 53
  • 79
thinker3
  • 12,771
  • 5
  • 30
  • 36

11 Answers11

250

Per @perreal, quoting variables is important, but because I read this post like five times before finding a simpler approach to the question at hand in the comments...

str='abcd/'
echo "${str: -1}"
=> /

Alternatively use ${str:0-1} as pointed out in the comments.

str='abcd*'
echo "${str:0-1}"
=> *

Note: The extra space in ${str: -1} is necessary, otherwise ${str:-1} would result in 1 being taken as the default value if str is null or empty.

${parameter:-word}
       Use Default Values.  If parameter is unset or null, the
       expansion of word is substituted.  Otherwise, the value of
       parameter is substituted.

Thanks to everyone who participated in the above; I've appropriately added +1's throughout the thread!

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
quickshiftin
  • 66,362
  • 10
  • 68
  • 89
  • 33
    NB: note the space inside `${std: -1}`, without the space this won't work. I ran into this and found that `${std:~0}` also works (with or without space). Not sure if this is documented behavior though, I haven't bothered to look it up. – Bart Jun 30 '14 at 13:49
  • 8
    it is documented. man bash says: Arithmetic expressions starting with a - must be separated by whitespace from the preceding : to be distinguished from the Use Default Values expansion – mighq Nov 08 '14 at 18:14
  • 3
    That's awesome, thanks for sharing. The BASH manpage is almost overwhelming to read though, I've been in there several times. I think they could make a college course around shell scripting lol. – quickshiftin Nov 08 '14 at 18:42
  • 4
    This solution does not work in an `.sh` script when trying to assign the retrieved character to a variable. For instance `declare LAST=$(echo "${str: -1}")` This will result in the nasty red bar showing a syntax error starting at the : – krb686 May 22 '15 at 18:33
  • 5
    if editor syntax checking does not like that space try `${str:0-1}` – ttt Oct 09 '17 at 21:53
  • 1
    Alternative syntax to `${str: -1}` is `${str::-1}`. – lucasvc Jan 04 '23 at 14:16
145

That's one of the reasons why you need to quote your variables:

echo "${str:$i:1}"

Otherwise, bash expands the variable and in this case does globbing before printing out. It is also better to quote the parameter to the script (in case you have a matching filename):

sh lash_ch.sh 'abcde*'

Also see the order of expansions in the bash reference manual. Variables are expanded before the filename expansion.

To get the last character you should just use -1 as the index since the negative indices count from the end of the string:

echo "${str: -1}"

The space after the colon (:) is REQUIRED.

This approach will not work without the space.

MikeSchinkel
  • 4,947
  • 4
  • 38
  • 46
perreal
  • 94,503
  • 21
  • 155
  • 181
  • 64
    BTW, negative indices count from right, so `"${1: -1}"` is enough. – choroba Jul 09 '13 at 08:06
  • 7
    You should reply as formal solution, not here in comments. – BMW Jan 16 '14 at 12:33
  • FYI: you can also do this in a "one-liner" as `echo "${str:$((${#str}-1)):1}"`. This allows you to vary the trailing characters returned as well. This work for me in bash v4.1.0(1) – SaxDaddy Oct 11 '15 at 00:52
  • 20
    @choroba's answer: Note the damn space! This always trips me up: `"${1:-1}` does not work"! – mxmlnkn May 05 '16 at 13:52
56

I know this is a very old thread, but no one mentioned which to me is the cleanest answer:

echo -n $str | tail -c 1

Note the -n is just so the echo doesn't include a newline at the end.

user5394399
  • 561
  • 4
  • 2
20

Every answer so far implies the word "shell" in the question equates to Bash.

This is how one could do that in a standard Bourne shell:

printf "%s" "$str" | tail -c 1
Torsten Bronger
  • 9,899
  • 7
  • 34
  • 41
phep
  • 531
  • 5
  • 8
  • 2
    `echo -n` as proposed by user5394399 avoids problems when `$str` contains special format characters. – Conrad Meyer Oct 24 '18 at 22:31
  • 2
    The `-n` switch is a bashism. It won't work in standard Bourne shell as dash, etc... Which was the point of my answer. – phep Oct 25 '18 at 08:16
  • 5
    Not quite a bashism, but POSIX recognizes it is implementation defined ("If the first operand is -n, or if any of the operands contain a character, the results are implementation-defined"). Your answer could instead be fixed with `printf "%s" "$str" | ...` :-). – Conrad Meyer Oct 26 '18 at 13:47
7

another solution using awk script:

last 1 char:

echo $str | awk '{print substr($0,length,1)}'

last 5 chars:

echo $str | awk '{print substr($0,length-5,5)}'
mr.baby123
  • 2,208
  • 23
  • 12
  • 1
    Not what the OP asked for, but this is the best solution I've found to be used with a file. Most other approaches won't work cat-ing a file to it, as in : `cat file | awk '{print substr($0,length,1)}'` Thanks! – xmar Aug 29 '19 at 09:50
5

Single line:

${str:${#str}-1:1}

Now:

echo "${str:${#str}-1:1}"
Eduardo Cuomo
  • 17,828
  • 6
  • 117
  • 94
  • 1
    Why do you use two pairs of parentheses? Also, since 4.2, you can use negative offsets, as shown in quickshiftin's answer: `echo "${str: -1}"` is enough (mind the space between `:` and `-`). – gniourf_gniourf Sep 14 '15 at 17:27
  • Two pairs of parentheses are used for arithmetic operations – Eduardo Cuomo Sep 14 '15 at 17:43
  • No. Please see [the relevant part of the manual](http://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion) where you'll read: _length_ and _offset_ are arithmetic expressions (see [Shell Arithmetic](http://www.gnu.org/software/bash/manual/bash.html#Shell-Arithmetic)); hence you're already in shell arithmetic and don't need any parentheses at all. Besides, if what you said were true, it would be more logical to have an _arithmetic expansion_ with `$((...))`. – gniourf_gniourf Sep 14 '15 at 17:54
  • @gniourf_gniourf you are right! I now understand your previous question. Answer updated – Eduardo Cuomo Sep 14 '15 at 18:04
4

Try:

"${str:$((${#str}-1)):1}"

For e.g.:

someone@mypc:~$ str="A random string*"; echo "$str"
A random string*
someone@mypc:~$ echo "${str:$((${#str}-1)):1}"
*
someone@mypc:~$ echo "${str:$((${#str}-2)):1}"
g
miken32
  • 42,008
  • 16
  • 111
  • 154
mark_infinite
  • 383
  • 1
  • 7
  • 13
2

For portability you can say "${s#"${s%?}"}":

#!/bin/sh
m=bzzzM n=bzzzN
for s in \
    'vv'  'w'   ''    'uu  ' ' uu ' '  uu' / \
    'ab?' 'a?b' '?ab' 'ab??' 'a??b' '??ab' / \
    'cd#' 'c#d' '#cd' 'cd##' 'c##d' '##cd' / \
    'ef%' 'e%f' '%ef' 'ef%%' 'e%%f' '%%ef' / \
    'gh*' 'g*h' '*gh' 'gh**' 'g**h' '**gh' / \
    'ij"' 'i"j' '"ij' "ij'"  "i'j"  "'ij"  / \
    'kl{' 'k{l' '{kl' 'kl{}' 'k{}l' '{}kl' / \
    'mn$' 'm$n' '$mn' 'mn$$' 'm$$n' '$$mn' /
do  case $s in
    (/) printf '\n' ;;
    (*) printf '.%s. ' "${s#"${s%?}"}" ;;
    esac
done

Output:

.v. .w. .. . . . . .u. 
.?. .b. .b. .?. .b. .b. 
.#. .d. .d. .#. .d. .d. 
.%. .f. .f. .%. .f. .f. 
.*. .h. .h. .*. .h. .h. 
.". .j. .j. .'. .j. .j. 
.{. .l. .l. .}. .l. .l. 
.$. .n. .n. .$. .n. .n. 
urznow
  • 1,576
  • 1
  • 4
  • 13
1
expr $str : '.*\(.\)'

Or

echo ${str: -1}
Zoe
  • 27,060
  • 21
  • 118
  • 148
0

For anyone interested in a pure POSIX method:

https://github.com/spiralofhope/shell-random/blob/master/live/sh/scripts/string-fetch-last-character.sh

#!/usr/bin/env  sh

string_fetch_last_character() {
  length_of_string=${#string}
  last_character="$string"
  i=1
  until [ $i -eq "$length_of_string" ]; do
    last_character="${last_character#?}"
    i=$(( i + 1 ))
  done

  printf  '%s'  "$last_character"
}


string_fetch_last_character  "$string"
spiralofhope
  • 134
  • 8
-3
echo $str | cut -c $((${#str}))

is a good approach

Qiu
  • 5,651
  • 10
  • 49
  • 56
pradeep
  • 7
  • 6