ChatGPT解决这个技术问题 Extra ChatGPT

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

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.


S
Stephen

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 ':'
 }

While this is working for the given problem, the answer of William below (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.
How would you then do the opposite of this? to echo out '1:2:3:4:'?
And how does one keep the part before the last separator? Apparently by using ${foo%:*}. # - from beginning; % - from end. #, % - shortest match; ##, %% - longest match.
If i want to get the last element from path, how should I use it? echo ${pwd##*/} does not work.
@Putnik that command sees pwd as a variable. Try dir=$(pwd); echo ${dir##*/}. Works for me!
a
a3nm

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.


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.
Some cut-and-paste fodder for people using spaces as delimiters: echo "1 2 3 4" | rev | cut -d " " -f1 | rev
the rev | cut -d -f1 | rev is so clever! Thanks! Helped me a bunch (my use case was rev | -d ' ' -f 2- | rev
I always forget about rev, was just what I needed! cut -b20- | rev | cut -b10- | rev
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
W
William Pursell

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]'

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 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.
How would I get the substring UNTIL the last field?
@blackjacx There are some quirks, but something like awk '{$NF=""; print $0}' FS=: OFS=: often works well enough.
N
Nicholas M T Elliott

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.


-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.
A
Abdallah_98

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 '[^:]*$'

Your second command was useful to me. Would you break it down so I can understand it better? Thank you.
D
Dennis Williamson

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]}

M
Mateusz Piotrowski
$ 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.


It will fail if the last item contains a \n, but for most cases is the most readable solution.
R
Rafael

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

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/"
0
021

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.


c
codeforester

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.


This doesn't work: it gives the last character of a, not the last field.
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...
R
Rafa Viotti

Using Bash.

$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo  \$${#}
0

Could have used echo ${!#} instead of eval echo \$${#}.
C
Crytis
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.


c
codeforester

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

s
sth
for x in `echo $str | tr ";" "\n"`; do echo $x; done

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.
s
slushy

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

C
Christoph Boeddeker

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.


b
bombs

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

python can do this directly: echo "1:2:3:4:5" | python -c "import sys; print(list(sys.stdin)[0].split(':')[-1])"
@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.