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
.
17 Answers
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 ':'
}

- 47,994
- 7
- 61
- 70
-
8While 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
-
12How would you then do the opposite of this? to echo out '1:2:3:4:'? – Dobz Jun 25 '14 at 11:44
-
20And 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
-
1If 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
-
1@Stan even shorter is `echo ${$(pwd)##*/}` – Cadoiz Jun 15 '21 at 22:37
-
This worked well for getting the name of the file at the end of a URL for me, by replacing `:` with `/`. – Matt Welke Jul 06 '22 at 05:03
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.

- 8,717
- 6
- 31
- 39
-
21This 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
-
6Some 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
-
2the 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
-
1I always forget about `rev`, was just what I needed! `cut -b20- | rev | cut -b10- | rev` – shearn89 Aug 17 '17 at 09:27
-
1I 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
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]'

- 204,365
- 48
- 270
- 300
-
7great 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
-
-
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
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.

- 3,643
- 2
- 19
- 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
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 '[^:]*$'

- 1,331
- 7
- 17
-
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
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]}

- 346,391
- 90
- 374
- 439
$ 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
.

- 8,029
- 10
- 53
- 79

- 189
- 1
- 2
-
3It 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
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

- 1,126
- 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
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.

- 323
- 2
- 17
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.

- 39,467
- 16
- 112
- 140

- 94
- 2
-
1This doesn't work: it gives the last _character_ of `a`, not the last _field_. – gniourf_gniourf Nov 13 '13 at 16:15
-
1True, that's the idea, if you know the length of the last field it's good. If not you have to use something else... – Ab Irato Nov 25 '13 at 13:25
Using Bash.
$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo \$${#}
0

- 9,998
- 4
- 42
- 62

- 327,991
- 56
- 259
- 343
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

- 3,277
- 1
- 18
- 24
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.

- 300
- 3
- 10
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

- 39,467
- 16
- 112
- 140

- 1,317
- 15
- 10
for x in `echo $str | tr ";" "\n"`; do echo $x; done

- 222,467
- 53
- 283
- 367

- 9
- 1
-
2This 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
improving from @mateusz-piotrowski and @user3133260 answer,
echo "a:b:c:d::e:: ::" | tr ':' ' ' | xargs | tr ' ' '\n' | tail -1
first, tr ':' ' ' -> replace ':' with whitespace
then, trim with xargs
after that, tr ' ' '\n' -> replace remained whitespace to newline
lastly, tail -1 -> get the last string

- 26,130
- 9
- 42
- 54

- 1
- 1
-
1If you want to contribute an answer according to [answer] please [edit] to make that more obvious. You can provide an answer based on an existing answer; giving credit to the exsting answers author is then very appropriate, thanks for doing that. I do however have trouble identifying the answer you are reffering to. Please use the link you get from the "Share" link beneath that answer to add an unambigous link here. – Yunnosch Nov 19 '22 at 06:25
-
1You know about the commenting privilege which you do not have, so well that you can even put it into words. You are aware of the rule https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead . In that situation please do not decide to misuse a different mechanism (an answer) for something it is not meant for and which you are not allowed yet to do. – Yunnosch Nov 19 '22 at 06:25
-
As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 22 '22 at 12:09
-
Thanks for the reminder @Yunnosch, as i am not a native english speaker, i may make mistakes, for all this time using SO just seeking/reading for answers of questions and not answering questions. – user8582041 Feb 13 '23 at 16:15
-
You only describe the code behaviour on a level which (with some experiece) is clear by the code itself. Please explain what it avhieves, i.e. wha the improvement is. – Yunnosch Feb 13 '23 at 16:21
-
sorry, as i am a rather forgetful person. so, the improvement to what i last remember is, to avoid failing if it has empty value between colon (:) or whitespaces. – user8582041 Feb 16 '23 at 14:03
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.

- 644
- 8
- 15