Without using sed
or awk
, only cut
, how do I get the last field when the number of fields are unknown or change with every line?
14 Answers
You could try something like this:
echo 'maps.google.com' | rev | cut -d'.' -f 1 | rev
Explanation
rev
reverses "maps.google.com" to bemoc.elgoog.spam
cut
uses dot (ie '.') as the delimiter, and chooses the first field, which ismoc
- lastly, we reverse it again to get
com
-
9It not using only
cut
but it's withoutsed
orawk
.So what OP think? Commented Mar 29, 2014 at 5:02 -
17@tom OP has asked more questions than just this in the last few hours. Based on our interactions with the OP we know that awk/sed/etc. are not allowed in his homework, but a reference to rev has not been made. So it was worth a shot– zedfoxusCommented Mar 29, 2014 at 5:03
-
4
-
3@zfus I think you require another
rev
also because in such condition likeecho 'www.google.com' | rev | cut -d'.' -f 1 | rev
. Commented Mar 29, 2014 at 5:06 -
3This is a very clever solution, but after all these years does
cut
really not have a way to access the final element indirectly? This feels hacky.– h0r53Commented Jul 21, 2020 at 18:15
Use a parameter expansion. This is much more efficient than any kind of external command, cut
(or grep
) included.
data=foo,bar,baz,qux
last=${data##*,}
See BashFAQ #100 for an introduction to native string manipulation in bash.
-
3@ErwinWessels: Because bash is really slow. Use bash to run pipelines, not to process data in bulk. I mean, this is great if you have one line of text already in a shell variable, or if you want to do
while IFS= read -ra array_var; do :;done <(cmd)
to process a few lines. But for a big file, rev|cut|rev is probably faster! (And of course awk will be faster than that.) Commented Dec 7, 2015 at 6:30 -
2@PeterCordes, awk will be faster for a big file, sure, but it takes a fair bit of input to overcome the constant-factor startup costs. (There also exist shells -- like ksh93 -- with performance closer to awk, where the syntax given in this answer remains valid; bash is exceptionally sluggish, but it's not even close to the only option available). Commented Dec 7, 2015 at 6:33
-
1Thanks @PeterCordes; as usual I guess each tool has its use cases. Commented Dec 7, 2015 at 11:50
-
2This is by far the fastest and most concise way of trimming down a single variable inside a
bash
script (assuming you're already using abash
script). No need to call anything external. Commented Jul 28, 2017 at 4:34 -
1@Balmipour, ...however,
rev
is specific to whatever OS you're using that provides it -- it's not standardized across all UNIX systems. See the chapter listing for the POSIX section on commands and utilities -- it's not there. And${var##prefix_pattern}
is not in fact bash-specific; it's in the POSIX sh standard, see the end of section 2.6.2 (linked), so unlikerev
, it's always available on any compliant shell. Commented Oct 13, 2017 at 11:15
It is not possible using just cut
. Here is a way using grep
:
grep -o '[^,]*$'
Replace the comma for other delimiters.
Explanation:
-o
(--only-matching
) only outputs the part of the input that matches the pattern (the default is to print the entire line if it contains a match).[^,]
is a character class that matches any character other than a comma.*
matches the preceding pattern zero or more time, so[^,]*
matches zero or more non‑comma characters.$
matches the end of the string.- Putting this together, the pattern matches zero or more non-comma characters at the end of the string.
- When there are multiple possible matches,
grep
prefers the one that starts earliest. So the entire last field will be matched.
Full example:
If we have a file called data.csv containing
one,two,three
foo,bar
then grep -o '[^,]*$' < data.csv
will output
three
bar
-
6To do the opposite, and find everything except the last field do:
grep -o '^.*,'
– ArielCommented Mar 11, 2016 at 6:59 -
2This was especially useful, because
rev
add an issue multibyte unicode characters in my case.– bric3Commented Dec 21, 2017 at 15:04 -
6I was trying to do this on MinGW but my grep version doesn't support -o, so I used
sed 's/^.*,//'
which replaces all characters up to and including the last comma with an empty string. Commented Apr 4, 2018 at 14:16
Without awk? But it's so simple with awk:
echo 'maps.google.com' | awk -F. '{print $NF}'
AWK is a way more powerful tool to have in your pocket.
-F is for field separator
$NF variable is the number of fields (also stands for the index of the last)
-
8This is universal and it works exactly as expected every time. In this scenario, using
cut
to achieve the OP's final output is like using a spoon to "cut" steak (pun intended :) ) .awk
is the steak knife. Commented Oct 11, 2018 at 1:04 -
9Avoid un-necessary use of
echo
that may slow down script for long files usingawk -F. '{print $NF}' <<< 'maps.google.com'
.– Anil_MCommented Oct 17, 2018 at 20:36 -
1For other searchers
-F .
stands for filed separator is.
For example if you want to separate based on/
character use-F /
– EsmaeelECommented Feb 18 at 17:40
There are multiple ways. You may use this too.
echo "Your string here"| tr ' ' '\n' | tail -n1
> here
Obviously, the blank space input for tr command should be replaced with the delimiter you need.
-
1This feels like the simplest answer to me, less pipes and clearer meaning Commented Jan 12, 2017 at 14:14
-
3That will not work for an entire file, which is what the OP probably meant.– AmirCommented Apr 26, 2017 at 7:24
This is the only solution possible for using nothing but cut:
echo "s.t.r.i.n.g." | cut -d'.' -f2- [repeat_following_part_forever_or_until_out_of_memory:] | cut -d'.' -f2-
Using this solution, the number of fields can indeed be unknown and vary from time to time. However as line length must not exceed LINE_MAX characters or fields, including the new-line character, then an arbitrary number of fields can never be part as a real condition of this solution.
Yes, a very silly solution but the only one that meets the criterias I think.
-
2Nice. Just take the last '.' off of "s.t.r.i.n.g." and this works.– MattCommented Aug 12, 2016 at 22:08
-
2I love when everyone says something is impossible and then someone chimes in with a working answer. Even if it is indeed very silly.– BeejorCommented Aug 23, 2016 at 3:13
-
1One could iterate
cut -f2-
in a loop until the output no longer changes.– loa_in_Commented Jun 25, 2018 at 11:11 -
I think you'd have to read the file line-by-line and then iterate the
cut -f2-
until it no longer changes. Otherwise you'd have to buffer the entire file. Commented May 14, 2021 at 18:02
It is better to use awk
while working with tabular data. If it can be achieved by awk
, why not use that? I suggest you do not waste your precious time, and use a handful of commands to get the job done.
Example:
# $NF refers to the last column in awk
ll | awk '{print $NF}'
-
And to change field separator, you can use
-F <separator>
, e.g.awk -F':' '{print $NF}'
(regular shell escaping applies). (Credit: stackoverflow.com/a/2609565/737956) Commented Apr 17, 2023 at 19:08 -
1Thank you for pointing that out. In addition to
$NF
, which refers to the last field, if you want to print fields other than the last then you can use the field number with a $ sign. For example:awk -F, '{print $2} /tmp/test.csv
this will print the second field rather than the last. Commented Apr 28, 2023 at 0:32
If your input string doesn't contain forward slashes then you can use basename
and a subshell:
$ basename "$(echo 'maps.google.com' | tr '.' '/')"
This doesn't use sed
or awk
but it also doesn't use cut
either, so I'm not quite sure if it qualifies as an answer to the question as its worded.
This doesn't work well if processing input strings that can contain forward slashes. A workaround for that situation would be to replace forward slash with some other character that you know isn't part of a valid input string. For example, the pipe (|
) character is also not allowed in filenames, so this would work:
$ basename "$(echo 'maps.google.com/some/url/things' | tr '/' '|' | tr '.' '/')" | tr '|' '/'
-
2Of course the pipe character is allowed in filenames. Just try
touch \|
. Commented May 14, 2021 at 18:04 -
I will change from downvote to upvote if you remove the false claim about
|
being not allowed in file names. But almost everytr
out there supports\0
or some other way of expressing the nul byte, and that definitely isn't allowed in file names, so you can use that as a place holder. Alsotr ab bc
just swaps alla
andb
without problems, so you can just avoid having to find a disallowed character entirely. Just pipe throughtr './' './'
once to swap before thebasename
and then again to swap back after.– mtraceurCommented Jun 26, 2021 at 11:34 -
Just realized I have a typo: "just pipe through
tr '/.' './'
once to swap before the basename and then again after".– mtraceurCommented Jul 9, 2021 at 1:31
An alternative using perl would be:
perl -pe 's/(.*) (.*)$/$2/' file
where you may change \t
for whichever the delimiter of file
is
the following implements A friend's suggestion
#!/bin/bash
rcut(){
nu="$( echo $1 | cut -d"$DELIM" -f 2- )"
if [ "$nu" != "$1" ]
then
rcut "$nu"
else
echo "$nu"
fi
}
$ export DELIM=.
$ rcut a.b.c.d
d
-
2You need double quotes around the arguments to
echo
in order for this to work reliably and robustly. See stackoverflow.com/questions/10067266/…– tripleeeCommented Dec 30, 2017 at 14:24
If you have a file named filelist.txt that is a list paths such as the following: c:/dir1/dir2/file1.h c:/dir1/dir2/dir3/file2.h
then you can do this: rev filelist.txt | cut -d"/" -f1 | rev
Adding an approach to this old question just for the fun of it:
$ cat input.file # file containing input that needs to be processed
a;b;c;d;e
1;2;3;4;5
no delimiter here
124;adsf;15454
foo;bar;is;null;info
$ cat tmp.sh # showing off the script to do the job
#!/bin/bash
delim=';'
while read -r line; do
while [[ "$line" =~ "$delim" ]]; do
line=$(cut -d"$delim" -f 2- <<<"$line")
done
echo "$line"
done < input.file
$ ./tmp.sh # output of above script/processed input file
e
5
no delimiter here
15454
info
Besides bash, only cut is used. Well, and echo, I guess.
-
1Meh, why not just remove cut completely and only use bash... x]
while read -r line; do echo ${line/*;}; done <input.file
yields the same result. Commented May 27, 2019 at 9:50
choose -1
choose supports negative indexing (the syntax is similar to Python's slices).
I realized if we just ensure a trailing delimiter exists, it works. So in my case I have comma and whitespace delimiters. I add a space at the end;
$ ans="a, b"
$ ans+=" "; echo ${ans} | tr ',' ' ' | tr -s ' ' | cut -d' ' -f2
b
-
And
ans="a, b, c"
producesb
, which does not meet the requirements of "number of fields are unknown or change with every line".– jwwCommented Mar 15, 2019 at 8:17
cut
command :)? why not any other Linux commands?sed
orawk
:perl -pe 's/^.+\s+([^\s]+)$/$1/'
.cut
supports something it doesn't. But I thought it was useful, in that it forces the reader to consider code that's easier to follow. I wanted a quick, simple way to usecut
without needing to use multiple syntaxes forawk
,grep
,sed
, etc. Therev
thing did the trick; very elegant, and something I've never considered (even if clunky for other situations). I also liked reading the other approaches from the other answers.find | cut -d. -f<last>
is the natural inclination