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 ':'
}
- 45,698
- 7
- 58
- 67
-
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
-
11How would you then do the opposite of this? to echo out '1:2:3:4:'? – Dobz Jun 25 '14 at 11:44
-
16And 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
-
@Stan even shorter is `echo ${$(pwd)##*/}` – Cadoiz Jun 15 '21 at 22:37
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,257
- 5
- 26
- 39
-
19This 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
-
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
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]'
- 190,037
- 45
- 260
- 285
-
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,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
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,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
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]}
- 324,833
- 88
- 366
- 429
$ 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.
- 6,866
- 9
- 49
- 75
- 149
- 1
- 2
-
2It 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,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
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.
- 303
- 2
- 14
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.
- 34,080
- 14
- 96
- 122
- 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,250
- 4
- 39
- 61
- 307,646
- 55
- 250
- 337
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.
- 190
- 2
- 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
- 34,080
- 14
- 96
- 122
- 1,059
- 13
- 8
for x in `echo $str | tr ";" "\n"`; do echo $x; done
- 211,504
- 50
- 270
- 362
- 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
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.
- 628
- 7
- 15
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,047
- 1
- 17
- 22
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
- 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