273

I would like to delete the last character of a string, I tried this little script :

#! /bin/sh 

t="lkj"
t=${t:-2}
echo $t

but it prints "lkj", what I am doing wrong?

0

13 Answers 13

276

With bash 4.2 and above, you can do:

${var::-1}

Example:

$ a=123
$ echo "${a::-1}"
12

Notice that for older bash ( for example, bash 3.2.5 on OS X), you should leave spaces between and after colons:

${var: : -1}
9
  • 13
    This works for bash version 4.2-alpha and above, too bad the version I have access to is earlier. :-/
    – h.j.k.
    Commented Jul 14, 2014 at 3:46
  • 4
    @iamaziz: From bash changelog, the negative length in ${var:offset:lenght} was added only in bash 4.2. Maybe OSX add its own patch for bash.
    – cuonglm
    Commented May 8, 2016 at 5:59
  • 2
    @cuonglm neither work :/
    – iamaziz
    Commented May 8, 2016 at 6:59
  • 2
    Doesn't work on mac.
    – shinzou
    Commented Aug 6, 2017 at 6:57
  • 1
    MACsters, look down to Russ's answer
    – P i
    Commented Jun 21, 2018 at 1:55
146

In a POSIX shell, the syntax ${t:-2} means something different - it expands to the value of t if t is set and non null, and otherwise to the value 2. To trim a single character by parameter expansion, the syntax you probably want is ${t%?}

Note that in ksh93, bash or zsh, ${t:(-2)} or ${t: -2} (note the space) are legal as a substring expansion but are probably not what you want, since they return the substring starting at a position 2 characters in from the end (i.e. it removes the first character i of the string ijk).

See the Shell Parameter Expansion section of the Bash Reference Manual for more info:

3
  • 7
    Would you care to explain what is the magic behind '%?' ?
    – afraisse
    Commented Apr 14, 2016 at 15:36
  • 16
    @afraisse ${parameter%word} removes the shortest suffix pattern matching word - see the Parameter Expansion section of man bash Commented Apr 14, 2016 at 18:15
  • 3
    This works well for Bash 4.1.2: ${t%?} for folks stuck with CentOS/RHEL 6.x
    – Joey T
    Commented Jan 12, 2017 at 23:04
113

Using sed it should be as fast as

sed 's/.$//'

Your single echo is then echo ljk | sed 's/.$//'.
Using this, the 1-line string could be any size.

5
  • 22
    Note that in the general case, it doesn't delete the last character of the string, but the last character of every line of the string. Commented Feb 1, 2016 at 10:23
  • 2
    sed -z 's/.$// will do what you're looking for, working with multiple lines too.
    – TWiStErRob
    Commented Feb 21, 2021 at 11:56
  • 1
    @TWiStErRob I get sed: illegal option -- z on macOS
    – Andy
    Commented Jun 14, 2021 at 11:30
  • @TWiStErRob: that may remove the last newline... Commented Apr 13, 2022 at 22:45
  • Yes, \n is a character, @LuisA.Florit :) You can amend the regex easily if you know more about your input, for example /.\n$/\n/ in your case to keep the last newline and remove the character before the last newline. I think even /.\n?$/\n/ might be an option to ensure a new line at the end of stream.
    – TWiStErRob
    Commented Apr 14, 2022 at 7:36
86

for removing the last n characters from a line that makes no use of sed OR awk:

> echo lkj | rev | cut -c (n+1)- | rev

so for example you can delete the last character one character using this:

> echo lkj | rev | cut -c 2- | rev

> lk

from rev manpage:

DESCRIPTION
The rev utility copies the specified files to the standard output, reversing the order of characters in every line. If no files are speci- fied, the standard input is read.

UPDATE:

if you don't know the length of the string, try:

$ x="lkj"
$ echo "${x%?}"
lk
0
69

A few options depending on the shell:

  • POSIX: t=${t%?}
  • Bourne: t=`expr " $t" : ' \(.*\).'`
  • zsh/yash: t=${t[1,-2]}
  • bash/zsh: t=${t:0:-1}
  • ksh93/bash/zsh/mksh: t=${t:0:${#t}-1}
  • ksh93/bash/zsh/mksh: t=${t/%?}
  • ksh93: t=${t/~(E).$/}
  • es: @ {t=$1} ~~ $t *?

Note that while all are supposed to strip the last character, you'll find that some implementations (those that don't support multi-byte characters) strip the last byte instead (so would likely corrupt the last character if it was multi-byte).

The expr variant assumes $t doesn't end in more than one newline character. It will also return a non-zero exit status if the resulting string ends up being 0 (or 000 or even -0 with some implementations). It could also give unexpected results if the string contains invalid characters.

10
  • Nice and thorough! But... I assume all of those shells support POSIX, so everyone should just use that one to be the most portable. Smallest character count, too!
    – Russ
    Commented Sep 16, 2016 at 3:20
  • @Russ, t=${t%?} is not Bourne but you're not likely to come across a Bourne shell nowadays. ${t%?} does work in all the other ones though. Commented Sep 16, 2016 at 6:53
  • No fish shell option given! Probably more popular these days than ksh93...
    – rien333
    Commented Nov 18, 2017 at 1:21
  • @rien333. I'd wait for the interface to stabilize a bit. fish is work in progress. 2.3.0 which introduced the string builtin was not released at the time of the Q&A. With the version I'm testing it on, you need string replace -r '(?s).\z' '' -- $t (and I'd expect they'd want to change that, they should change the flags they pass to PCRE) or more convoluted ones. It also deals poorly with newline characters, and I know they're planning on changing that as well. Commented Nov 18, 2017 at 8:10
  • 1
    @Timo, not sure what you mean. None of the solutions I gave are bash-specific. The only one invented by bash AFAIK is t=${t:0:-1} (extending the ${var:offset:length} ksh93 operator). For each, I gave which shells support it. You can use the Bourne/POSIX ones from and modern sh. Commented Nov 16, 2020 at 14:02
56

The most portable, and shortest, answer is almost certainly:

${t%?}

This works in bash, sh, ash, dash, busybox/ash, zsh, ksh, etc.

It works by using old-school shell parameter expansion. Specifically, the % specifies to remove the smallest matching suffix of parameter t that matches the glob pattern ? (ie: any character).

See "Remove Smallest Suffix Pattern" here for a (much) more detailed explanation and more background. Also see the docs for your shell (eg: man bash) under "parameter expansion".


As a side note, if you wanted to remove the first character instead, you would use ${t#?}, since # matches from the front of the string (prefix) instead of the back (suffix).

Also worth noting is that both % and # have %% and ## versions, which match the longest version of the given pattern instead of the shortest. Both ${t%%?} and ${t##?} would do the same as their single operator in this case, though (so don't add the useless extra character). This is because the given ? pattern only matches a single character. Mix in a * with some non-wildcards and things get more interesting with %% and ##.

Understanding parameter expansions, or at least knowing about their existence and knowing how to look them up, is incredibly useful for writing and deciphering shell scripts of many flavors. Parameter expansions often look like arcane shell voodoo to many people because... well... they are arcane shell voodoo (although pretty well documented if you know to look for "parameter expansion"). Definitely good to have in the tool belt when you're stuck in a shell, though.

1
  • 1
    Short and sweet, and works on both MacOS and Linux!
    – dbernard
    Commented Aug 14, 2019 at 1:29
19
t=lkj
echo ${t:0:${#t}-1}

You get a substring from 0 to the string length -1. Note however that this substraction is bash specific, and won't work on other shells.

For instance, dash isn't able to parse even

echo ${t:0:$(expr ${#t} - 1)}

For example, on Ubuntu, /bin/sh is dash

18

You can also use head to print out all but the last character.

$ s='i am a string'
$ news=$(echo -n $s | head -c -1)
$ echo $news
i am a strin

But unfortunately some versions of head do not include the leading - option. This is the case for the head that comes with OS X.

7

Some refinements. To remove more than one character, you can add multiple question marks. For example, to remove the last two characters from the variable: $SRC_IP_MSG, you can use:

SRC_IP_MSG=${SRC_IP_MSG%??}
6

It is easy enough to do using regular expression:

n=2
echo "lkj" | sed "s/\(.*\).\{$n\}/\1/"
5

Just to complete some possible usages of pure bash:

#!/bin/bash

# Testing substring removal
STR="Exemple string with trailing whitespace "
echo "'$STR'"
echo "Removed trailing whitespace: '${STR:0:${#STR}-1}'"
echo "Removed trailing whitespace: '${STR/%\ /}'"

The first syntax takes a substring from a string, the syntax is
${STRING:OFFSET:LENGTH}
For the second one, do notice the % sign, which means 'from end of line' and the syntax is
${STRING/PATTERN/SUBSTITUTION}

And here are two shorter forms of the above mentioned

echo "Removed trailing whitespace: '${STR::-1}'"
echo "Removed trailing whitespace: '${STR%\ }'"

Here notice again the % sign, meaning 'Remove ( that is, replace with '' ) the shortest matched pattern (here represented by escaped space '\ ' from the end of the PARAMETER - here named STR

1

As we can also use php in command line, or shell scripts. It is sometimes useful for surgical parsing.

php -r "echo substr('Hello', 0, -1);" 
// Output hell

With piping:

echo "hello" | php -r "echo substr(trim(fgets(STDIN)), 0, -1);"
// Output hell
0

In ksh:

echo ${ORACLE_SID/%?/}

You must log in to answer this question.

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