352

Suppose I have the string 1:2:3:4:5 and I want to get its last field (5 in this case). How do I do that using Bash? I tried cut, but I don't know how to specify the last field with -f.

JonyD
  • 1,127
  • 1
  • 20
  • 32
cd1
  • 14,907
  • 11
  • 44
  • 45

17 Answers17

517

You can use string operators:

$ foo=1:2:3:4:5
$ echo ${foo##*:}
5

This trims everything from the front until a ':', greedily.

${foo  <-- from variable foo
  ##   <-- greedy front trim
  *    <-- matches anything
  :    <-- until the last ':'
 }
Stephen
  • 45,698
  • 7
  • 58
  • 67
  • 8
    While this is working for the given problem, the answer of William below (http://stackoverflow.com/a/3163857/520162) also returns `5` if the string is `1:2:3:4:5:` (while using the string operators yields an empty result). This is especially handy when parsing paths that could contain (or not) a finishing `/` character. – eckes Jan 23 '13 at 15:23
  • 11
    How would you then do the opposite of this? to echo out '1:2:3:4:'? – Dobz Jun 25 '14 at 11:44
  • 16
    And how does one keep the part before the last separator? Apparently by using `${foo%:*}`. `#` - from beginning; `%` - from end. `#`, `%` - shortest match; `##`, `%%` - longest match. – Mihai Danila Jul 09 '14 at 14:07
  • 1
    If i want to get the last element from path, how should I use it? `echo ${pwd##*/}` does not work. – Putnik Feb 11 '16 at 22:33
  • 2
    @Putnik that command sees `pwd` as a variable. Try `dir=$(pwd); echo ${dir##*/}`. Works for me! – Stan Strum Dec 17 '17 at 04:22
  • @Stephen How do I use dot (.) as a delimiter? – Manoj Jun 27 '18 at 05:58
  • @Stan even shorter is `echo ${$(pwd)##*/}` – Cadoiz Jun 15 '21 at 22:37
386

Another way is to reverse before and after cut:

$ echo ab:cd:ef | rev | cut -d: -f1 | rev
ef

This makes it very easy to get the last but one field, or any range of fields numbered from the end.

a3nm
  • 8,257
  • 5
  • 26
  • 39
  • 19
    This answer is nice because it uses 'cut', which the author is (presumably) already familiar. Plus, I like this answer because *I* am using 'cut' and had this exact question, hence finding this thread via search. – Dannid Jan 14 '13 at 20:50
  • 6
    Some cut-and-paste fodder for people using spaces as delimiters: `echo "1 2 3 4" | rev | cut -d " " -f1 | rev` – funroll Aug 12 '13 at 19:51
  • 2
    the rev | cut -d -f1 | rev is so clever! Thanks! Helped me a bunch (my use case was rev | -d ' ' -f 2- | rev – EdgeCaseBerg Sep 08 '13 at 05:01
  • 1
    I always forget about `rev`, was just what I needed! `cut -b20- | rev | cut -b10- | rev` – shearn89 Aug 17 '17 at 09:27
  • I ended up with this solution, my attempt o cut file paths with "awk -F "/" '{print $NF}' " broke somewhat for me, as file names including white spaces got also cut apart – THX Feb 26 '18 at 14:34
  • Beware: `rev` is not safe with multi-byte Unicode characters! Therefore some corner cases might not work with `rev`. – t0r0X Mar 01 '18 at 23:11
  • @t0r0X: Are you sure? On my machine, with `LC_ALL=en_US.utf8`, running `echo 'hé' | rev` correctly returns `éh`. I have to run `echo 'hé' | LC_ALL=C rev` to get an error: `rev: stdin: Invalid or incomplete multibyte or wide character`. – a3nm Mar 02 '18 at 00:55
  • except that: `-sh: rev: command not found` on my NAS, seems like rev is not so common, otherwise I agree it better answers a question about cut – papo Nov 19 '18 at 10:29
  • Fantastic! I wanted to grab only the top-level and second-level domains from a domain name. With `cut`, I can turn things like www.google.com into google.com! – b_laoshi Nov 29 '18 at 07:54
  • If you want to extract the TLD out of a list of domains: `cat domains.txt | rev | cut -d. -f2 | rev | sort | uniq -c | sort -rn` – gies0r Apr 30 '20 at 13:27
  • This is by far the best answer if you are processing a whole file using cat + grep, thanks a lot for coming up with it!! – mrArias Nov 30 '21 at 14:39
97

It's difficult to get the last field using cut, but here are some solutions in awk and perl

echo 1:2:3:4:5 | awk -F: '{print $NF}'
echo 1:2:3:4:5 | perl -F: -wane 'print $F[-1]'
William Pursell
  • 190,037
  • 45
  • 260
  • 285
  • 7
    great advantage of this solution over the accepted answer: it also matches paths that contain or do not contain a finishing `/` character: `/a/b/c/d` and `/a/b/c/d/` yield the same result (`d`) when processing `pwd | awk -F/ '{print $NF}'`. The accepted answer results in an empty result in the case of `/a/b/c/d/` – eckes Jan 23 '13 at 15:20
  • @eckes In case of AWK solution, on GNU bash, version 4.3.48(1)-release that's not true, as it matters whenever you have trailing slash or not. Simply put AWK will use `/` as delimiter, and if your path is `/my/path/dir/` it will use value after last delimiter, which is simply an empty string. So it's best to avoid trailing slash if you need to do such a thing like I do. – stamster May 21 '18 at 11:52
  • How would I get the substring UNTIL the last field? – blackjacx Jun 09 '20 at 16:28
  • 1
    @blackjacx There are some quirks, but something like `awk '{$NF=""; print $0}' FS=: OFS=:` often works well enough. – William Pursell Jun 09 '20 at 16:50
35

Assuming fairly simple usage (no escaping of the delimiter, for example), you can use grep:

$ echo "1:2:3:4:5" | grep -oE "[^:]+$"
5

Breakdown - find all the characters not the delimiter ([^:]) at the end of the line ($). -o only prints the matching part.

Nicholas M T Elliott
  • 3,435
  • 2
  • 18
  • 15
  • 1
    -E means using extended syntax; [^...] means anything but the listed char(s); + one or more such hits (will take the maximum possible length for the pattern; this item is a gnu extension) - for the example the separating char(s) are the colon. – Alexander Stohr Oct 17 '19 at 11:36
20

You could try something like this if you want to use cut:

echo "1:2:3:4:5" | cut -d ":" -f5

You can also use grep try like this :

echo " 1:2:3:4:5" | grep -o '[^:]*$'
Abdallah_98
  • 1,233
  • 5
  • 15
  • Your second command was useful to me. Would you break it down so I can understand it better? Thank you. – John Mar 02 '21 at 15:09
19

One way:

var1="1:2:3:4:5"
var2=${var1##*:}

Another, using an array:

var1="1:2:3:4:5"
saveIFS=$IFS
IFS=":"
var2=($var1)
IFS=$saveIFS
var2=${var2[@]: -1}

Yet another with an array:

var1="1:2:3:4:5"
saveIFS=$IFS
IFS=":"
var2=($var1)
IFS=$saveIFS
count=${#var2[@]}
var2=${var2[$count-1]}

Using Bash (version >= 3.2) regular expressions:

var1="1:2:3:4:5"
[[ $var1 =~ :([^:]*)$ ]]
var2=${BASH_REMATCH[1]}
Dennis Williamson
  • 324,833
  • 88
  • 366
  • 429
13
$ echo "a b c d e" | tr ' ' '\n' | tail -1
e

Simply translate the delimiter into a newline and choose the last entry with tail -1.

Mateusz Piotrowski
  • 6,866
  • 9
  • 49
  • 75
user3133260
  • 149
  • 1
  • 2
  • 2
    It will fail if the last item contains a `\n`, but for most cases is the most readable solution. – Yajo Jul 30 '14 at 10:13
7

Using sed:

$ echo '1:2:3:4:5' | sed 's/.*://' # => 5

$ echo '' | sed 's/.*://' # => (empty)

$ echo ':' | sed 's/.*://' # => (empty)
$ echo ':b' | sed 's/.*://' # => b
$ echo '::c' | sed 's/.*://' # => c

$ echo 'a' | sed 's/.*://' # => a
$ echo 'a:' | sed 's/.*://' # => (empty)
$ echo 'a:b' | sed 's/.*://' # => b
$ echo 'a::c' | sed 's/.*://' # => c
Rafael
  • 1,046
  • 11
  • 16
  • given the output of many utilities is in the form of the original file name followed by colon (:) followed by the utility's output (${path}:${output}), this is incredibly useful for adding your own control character like TAB $'\t' or unit separator $'\037' etc. after that final colon. example for adding a TAB at the final colon of file output: file ~/yourPath/* | sed "s/\(.*:\)\(.*\)/\1"$'\t'"\2/" – spioter Sep 03 '20 at 13:48
4

There are many good answers here, but still I want to share this one using basename :

 basename $(echo "a:b:c:d:e" | tr ':' '/')

However it will fail if there are already some '/' in your string. If slash / is your delimiter then you just have to (and should) use basename.

It's not the best answer but it just shows how you can be creative using bash commands.

021
  • 303
  • 2
  • 14
3

If your last field is a single character, you could do this:

a="1:2:3:4:5"

echo ${a: -1}
echo ${a:(-1)}

Check string manipulation in bash.

codeforester
  • 34,080
  • 14
  • 96
  • 122
Ab Irato
  • 94
  • 2
2

Using Bash.

$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo  \$${#}
0
Rafa Viotti
  • 9,250
  • 4
  • 39
  • 61
ghostdog74
  • 307,646
  • 55
  • 250
  • 337
1
echo "a:b:c:d:e"|xargs -d : -n1|tail -1

First use xargs split it using ":",-n1 means every line only have one part.Then,pring the last part.

Crytis
  • 190
  • 2
  • 10
1

A solution using the read builtin:

IFS=':' read -a fields <<< "1:2:3:4:5"
echo "${fields[4]}"

Or, to make it more generic:

echo "${fields[-1]}" # prints the last item
codeforester
  • 34,080
  • 14
  • 96
  • 122
baz
  • 1,059
  • 13
  • 8
0
for x in `echo $str | tr ";" "\n"`; do echo $x; done
sth
  • 211,504
  • 50
  • 270
  • 362
  • 2
    This runs into problems if there is whitespace in any of the fields. Also, it does not directly address the question of retrieving the *last* field. – chepner Jun 22 '12 at 12:58
0

For those that comfortable with Python, https://github.com/Russell91/pythonpy is a nice choice to solve this problem.

$ echo "a:b:c:d:e" | py -x 'x.split(":")[-1]'

From the pythonpy help: -x treat each row of stdin as x.

With that tool, it is easy to write python code that gets applied to the input.

Edit (Dec 2020): Pythonpy is no longer online. Here is an alternative:

$ echo "a:b:c:d:e" | python -c 'import sys; sys.stdout.write(sys.stdin.read().split(":")[-1])'

it contains more boilerplate code (i.e. sys.stdout.read/write) but requires only std libraries from python.

0

Regex matching in sed is greedy (always goes to the last occurrence), which you can use to your advantage here:

$ foo=1:2:3:4:5
$ echo ${foo} | sed "s/.*://"
5
slushy
  • 3,047
  • 1
  • 17
  • 22
-1

If you like python and have an option to install a package, you can use this python utility.

# install pythonp
pythonp -m pip install pythonp

echo "1:2:3:4:5" | pythonp "l.split(':')[-1]"
5
bombs
  • 1,384
  • 10
  • 24
  • python can do this directly: `echo "1:2:3:4:5" | python -c "import sys; print(list(sys.stdin)[0].split(':')[-1])"` – MortenB Mar 06 '19 at 18:12
  • @MortenB You are mistaken. The whole purpose of `pythonp` package is to make you do the same things as `python -c` with fewer character typings. Please have a look at the README in the repository. – bombs Mar 08 '19 at 05:50