How to split a string in shell and get the last field
Asked Answered
P

17

399

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.

Perennial answered 1/7, 2010 at 23:29 Comment(0)
A
593

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 ':'
 }
Adieu answered 2/7, 2010 at 0:5 Comment(8)
While this is working for the given problem, the answer of William below (https://mcmap.net/q/66528/-how-to-split-a-string-in-shell-and-get-the-last-field) 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.Jalousie
How would you then do the opposite of this? to echo out '1:2:3:4:'?Gahnite
And how does one keep the part before the last separator? Apparently by using ${foo%:*}. # - from beginning; % - from end. #, % - shortest match; ##, %% - longest match.Habitude
If i want to get the last element from path, how should I use it? echo ${pwd##*/} does not work.Jeramie
@Jeramie that command sees pwd as a variable. Try dir=$(pwd); echo ${dir##*/}. Works for me!Sheply
@Adieu How do I use dot (.) as a delimiter?Salo
@Stan even shorter is echo ${$(pwd)##*/}Schmidt
This worked well for getting the name of the file at the end of a URL for me, by replacing : with /.Sword
T
413

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.

Trevar answered 3/2, 2012 at 8:39 Comment(11)
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.Stud
Some cut-and-paste fodder for people using spaces as delimiters: echo "1 2 3 4" | rev | cut -d " " -f1 | revAsylum
the rev | cut -d -f1 | rev is so clever! Thanks! Helped me a bunch (my use case was rev | -d ' ' -f 2- | revTangerine
I always forget about rev, was just what I needed! cut -b20- | rev | cut -b10- | revSorrow
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 apartMonarchism
Beware: rev is not safe with multi-byte Unicode characters! Therefore some corner cases might not work with rev.Bead
@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.Trevar
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 cutQuasijudicial
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!Interval
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 -rnOzonize
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!!Son
H
116

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]'
Hylophagous answered 2/7, 2010 at 7:9 Comment(4)
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/Jalousie
@Jalousie 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.Destructor
How would I get the substring UNTIL the last field?Heraclitean
@Heraclitean There are some quirks, but something like awk '{$NF=""; print $0}' FS=: OFS=: often works well enough.Hylophagous
O
41

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.

Offhand answered 1/7, 2010 at 23:39 Comment(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.Progressist
C
29

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 '[^:]*$'
Curculio answered 6/2, 2021 at 9:40 Comment(1)
Your second command was useful to me. Would you break it down so I can understand it better? Thank you.Revitalize
S
20

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]}
Sternpost answered 2/7, 2010 at 0:5 Comment(0)
C
17
$ 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.

Conceivable answered 24/12, 2013 at 19:4 Comment(1)
It will fail if the last item contains a \n, but for most cases is the most readable solution.Hirst
H
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
Hybridism answered 10/11, 2016 at 10:9 Comment(1)
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/"Illuminati
D
5

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.

Deflexed answered 26/4, 2016 at 11:33 Comment(0)
M
4

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.

Miramirabeau answered 13/11, 2013 at 16:10 Comment(2)
This doesn't work: it gives the last character of a, not the last field.Constancy
True, that's the idea, if you know the length of the last field it's good. If not you have to use something else...Miramirabeau
C
2

Using Bash.

$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo  \$${#}
0
Crispin answered 2/7, 2010 at 1:16 Comment(1)
Could have used echo ${!#} instead of eval echo \$${#}.Signory
D
2

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
Delaminate answered 29/1, 2019 at 15:12 Comment(0)
J
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.

Jokjakarta answered 7/12, 2016 at 6:51 Comment(0)
S
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
Soybean answered 24/11, 2017 at 19:27 Comment(0)
H
0
for x in `echo $str | tr ";" "\n"`; do echo $x; done
Haakon answered 22/6, 2012 at 2:55 Comment(1)
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.Dewain
M
0

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

Marleah answered 18/11, 2022 at 16:15 Comment(6)
If you want to contribute an answer according to How 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.Yaelyager
You 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 meta.stackexchange.com/questions/214173/… . 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.Yaelyager
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.Infestation
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.Marleah
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.Yaelyager
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.Marleah
A
-2

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.

Adhesive answered 19/2, 2018 at 15:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.