13

suppose , if there is a file consisting of following lines , if they are

12345 567 7878 66

   er3   t45t y6y46y 


 4y6 y656y y5y

   46y6 65y7 y66uyuy

 yy46y6y

The output has to look like:

66

y6y46y

y5y

y66uyuyy

y46y6y

I have tried the command sed 's/.* //g' filename and several other sed commands, but it is not working.

Can I know what is the exact sed command?

2
  • Is it a must to use sed?
    – Vombat
    Commented Jan 27, 2015 at 20:48
  • Your sed command deletes the entire second text-containing line (a line that has trailing whitespace) using my admittedly ancient version of sed. MacOS: man sed says "BSD May 10, 2005". Commented Jun 18, 2022 at 17:49

11 Answers 11

9

You can try :

  • sed 's/.* //'
  • awk '{print $NF}'

If your file has lines ending with whitespaces, you can remove said trailing whitespaces beforehand with sed 's/ +$//'. Both actions can be combined in the one-liner:

  • sed -e 's/ +$//' -e 's/.* //'
3
  • The sed 's/.* //' command was given in the question, and it fails for lines that end with space  — and the example text in the question has one such line. Commented Jul 9, 2022 at 20:19
  • @G-ManSays'ReinstateMonica' Indeed. Added suggestion to deal with this case with a separate command.
    – Uriel
    Commented Jul 10, 2022 at 21:10
  • Thanks. You should be able to filter for the space then find the last word by joining commands sed -e 's/ +$//' -e 's/.* //'
    – John 9631
    Commented Jul 21, 2022 at 4:09
8
awk '{print $NF}'
sed 's/[[:blank:]]*$//;s/.*[[:blank:]]//'

That would still print an empty line for every blank line (the whole line with awk as the $ operator would be applied to 0 which would yield the full record). To avoid it:

awk 'NF{print $NF}'
sed 's/[[:blank:]]*$//;s/.*[[:blank:]]//;/./!d'
2
  • Single expression alternative: sed -n 's/.*[[:blank:]]\+\([^[:blank:]]\+\)[[:blank:]]*$/\1/p'.
    – jimmij
    Commented Jan 28, 2015 at 1:37
  • @jimmij - that one doesn't work if the last not blank sequence is also the first and there are no blanks preceding it. Also, you might as well just do .* at the tail, probably - you rule out anything but trailing blanks anyway w/ .*[^[:blank:]].
    – mikeserv
    Commented Jan 28, 2015 at 4:20
7

The awk variable $NF is the last field of every record; you can use it to print only the last fields of your file like so:

awk '{print $NF}' file
1
  • 1
    The awk variable is actually NF which means the Number of Fields. $NF expands to the dollar-sign followed by a number, which then expands to the value of the last field - but only because $0 is the whole line and $1 is the first word on the line. This mechanism can be used to select the 2nd last word on a line using $(NF-1), or the 3rd last word using $(NF-2), etc. Commented Nov 25, 2021 at 16:32
5

You're almost there. Just specify the last word:

sed 's/^.* \([^ ][^ ]*\)/\1/g'

What it does:

  1. '^.* ' deletes everything within the start of the line and any spaces.
  2. '\(...)\' matches a pattern and returns it as \1.
  3. '[^ ]' matches anything without a space in it.

(Edited to add better solution. Thanks Hildred!)

2
  • 1
    Here is a shorter expression: sed -r 's/.* ([^ ]+)/\1/g' if extended regular expressions are allowed, which is usually the case.
    – mkalkov
    Commented Jan 28, 2015 at 9:10
  • Shorter version, using replacing what your do not want to keep rather that what you want to keep: sed 's/.* //'
    – Uriel
    Commented Jul 24, 2017 at 13:15
2

You could use some adequate pattern of grep instead of sed, for instance:

grep -o "[a-Z0-9]*$"

In this example, the [...] contains ranges of characters considered appropriate for a "word" (alphanumerics in this case, other symbols could be added, some of which must be escaped).

1
  • 3
    That assumes there's no blank at the end of the line. a-Z as a range doesn't make much sense, even in ASCII-based locales. Note that -o is a GNU extension. Commented Jan 27, 2015 at 21:23
0

If you qualify word to mean any sequence of 1 or more non-blank characters then the answer is definitely yes, and it is very simply done as well. This is because [[:blank:]]* and [^[:blank:]]* are boolean complements and - provided all characters in a string are complete - [[:blank:]]*U[^[:blank:]]* can describe any possible string in much the same way .* does.

If an incomplete character or otherwise invalid byte sequence exists within a string neither can successfully describe it head to tail - as can sometimes occur when interpreting a string in the wrong encoding. To ensure a complete character per byte in any string, the C locale can be forced like:

LC_ALL=C sed ...

...which would avoid any issues describing the string from head to tail with an all-inclusive pattern such as .* or ([ ]*[^ ]*)*

A fully complementary pattern can repeat as many times as is necessary from left to right the length of any string to land on the last possible occurrence without any break in the pattern. This is, definitively, regular language.

BRE:

sed 's/\(\([^[:blank:]]*\)[[:blank:]]*\)*/\2/'

ERE:

sed -E 's/(([^[:blank:]]*)[[:blank:]]*)*/\2/'

Both of these versions will still print blank lines, and this is because the Kleene * star matches zero or more occurrences of a pattern. It first matches zero or more not blank characters, then zero or more blank characters, then zero or more occurrences of the grouped matches until it has matched the string in its entirety.

Having matched all of this, the magic happens in the replacement - the references returned by groups \1 and \2 are the last occurrences of each. So when the replacement is made all of the string is replaced with only the last occurrence on a line of zero or more not blank characters - or the subgroup \2.

Of course this works for any possible string - even an empty one - which means both forms will print newline characters for lines which contain only blank characters or none at all. To handle this there are a couple things you can do, but first let's make the character class a little easier to type:

b='[:blank:]'

Now, to print only if a line contains one or more not blank characters you can do:

BRE:

sed -n "s/\(\([^$b]*\)[$b]*\)*/\2/;/./p"

ERE:

sed -En "/[^$b]/s/(([^$b]*)[$b]*)*/\2/p"
  1. BRE case - the substitution is always performed and only pattern spaces with at least one remaining character are printed.
  2. ERE case - the substitution is only ever attempted on a pattern space containing at least one not blank char.

Either form will work with either method - as long as the syntax is correct.

The -n switch disables auto-printing of pattern space, and the p flag to the s///ubstitution or the /address/ commands prints its results only if successful.

This same logic can be applied to get any {num}th occurrence, as well, like:

BRE:

sed -n "s/\([$b]*\([^$b]\{1,\}\)\)\{num\}.*/\2/p"

ERE:

sed -En "s/([$b]*([^$b]+)){num}.*/\2/p"

...where the num in both regexps can be replaced with a number to print only the {num}th specified occurrence of a sequence of not blank characters. A slightly different form is used here to ensure the count is not skewed for leading space in a string.

Note that the -E ERE switch to sed is supported in both BSD and GNU versions, though it is not yet POSIX standard syntax.

5
  • Nice explanations, nice hack, but note it won't work with traditional sed implementations (like Solaris /usr/bin/sed) and is going to be more expensive than the more straightforward approach (exhausts memory with input lines more than 25 characters long with the sed_su3 from the Heirloom toolchest for instance). So, though I like the answer, I wouldn't recommend that approach. Commented Feb 2, 2015 at 22:07
  • Doesn't seem to work in FreeBSD either. Commented Feb 2, 2015 at 22:14
  • @StéphaneChazelas - yeah, the performance is really awful for a thing like this, but it can be very effective for picking out numbered occurrences. And for an end of line case s/.* \([^[:blank:]]\{1,\}\).*/\1/ is far better, but it is more difficult when multiple lines are involved. Just the other day, though, I discovered 's/\(\n\)*/\1/g;s/\n\(\n.*\)*/&&/[num];s///[samenum] can pretty effectively shore that up. Anyway, so long as there is no glaring error in the logic then I'm happy - i just thought I must have missed something.
    – mikeserv
    Commented Feb 3, 2015 at 0:02
  • @StéphaneChazelas - oh, and about the older seds - that's a little weird - it should be sound according to standard. xrat says... The standard developers regarded the common historical behavior, which supported "\n*", but not "\n\{min,max\}", "\(...\)*", or "\(...\)\{min,max\}", as a non-intentional result of a specific implementation, and they supported both duplication and interval expressions following subexpressions and back-references.
    – mikeserv
    Commented Feb 3, 2015 at 0:04
  • @StéphaneChazelas - And the standard says... If the subexpression referenced by the back-reference matches more than one string because of an asterisk ( '*' ) or an interval expression (see item (5)), the back-reference shall match the last (rightmost) of these strings. I'm pretty sure I tested this w/ minised though - certainly I was testing something weird w/ minised the other day, anyway.
    – mikeserv
    Commented Feb 3, 2015 at 0:08
0
sed 's/^ star.star //'  filename  or sed 's/^[[:blank:]]star.star[[:blank:]]//' filename

Analysis:

  • s -- substitute

  • / --beginning of expression to look for

  • ^ -- from the beginning of the line

  • [[:blank:]]* -- if there are blanks at the beginning of line in case

  • .* -- any character

  • [[:blank:]] -- and a blank character

  • / -- beginning of expression to substitute

  • / -- end of the command syntax

PS: i have written star in the commannd.

3
  • How would this be applied to the data given in the question?
    – Kusalananda
    Commented May 12, 2020 at 10:15
  • @Scott s/.*[[:blank:]]// would work unless there are blanks at the end of a line.
    – Kusalananda
    Commented May 12, 2020 at 10:50
  • "i have written star in the commannd" - why not just use * directly? Commented Feb 9, 2023 at 21:27
0

Using Raku (formerly known as Perl_6)

raku -ne 'put .words[*-1] // "" ;'

OR

raku -ne 'put .words.tail // "" ;'

Briefly, Raku's -ne non-autoprinting linewise flags are used. Input text is broken into whitespace-separated words (The leading . dot on .words means input couples to the $_ topic variable, by default).

Each line's last word is taken using either an index ([*-1]) or the tail command. Finally, because lines could be entirely blank, Raku's // "defined-OR" operator is used to return a "" empty string, preserving the original linespacing. Note, instead of "" empty string Raku also accepts () or the keyword Empty here, both signifying an empty list.

Sample Input:

12345 567 7878 66

   er3   t45t y6y46y 


 4y6 y656y y5y

   46y6 65y7 y66uyuy

 yy46y6y

Sample Output:

66

y6y46y


y5y

y66uyuy

yy46y6y

For persons preferring a regex approach, any one of the following four one-liners works. The first two leave trailing whitespace intact, while the latter two trim trailing whitespace:

raku -ne 'S/^ <(.* \s)> \S+? \s* $//.put;'  
#OR
raku -pe 's/^ <(.* \s)> \S+? \s* $//;' 

#OR

raku -ne 'S/^ .* \s (\S+?) \s* $/$0/.put;'
#OR
raku -pe 's/^ .* \s (\S+?) \s* $/$0/;' 

Finally, if the desired output is one with truly blank lines removed (retaining lines with the desired last word, and/or lines with whitespace characters only), that's straight-forward as well:

~$ raku -ne 'if .chars {put .words.tail // ""};' file
66
y6y46y
y5y
y66uyuy
yy46y6y

#OR

~$ raku -ne 'put .words.tail // "" if .chars;' file
66
y6y46y
y5y
y66uyuy
yy46y6y

https://raku.org

3
  • 1
    FYI if .chars {put .words.tail}; could be just .put with .words.tail. And that would be safer, in the sense that it would work even if the input contained lines that contained (nothing but) white space characters.
    – raiph
    Commented Jul 6, 2022 at 2:30
  • @raiph I've updated that code to retain whitespace-only lines, the reasoning being that sometimes blocks of whitespace-only lines are used as section-separators in documents (or code). Your code is perfectly valid for removing even whitespace-only lines, although myself I might do either put .words[*-1] if .words or put .words.tail if .words instead. Commented Jul 7, 2022 at 13:37
-1

Yes. The following sed command first removes all trailing whitespaces (s/ *$//) and then everything up to and including the last whitespace (s/.* //). It is probably worthwhile replacing literal whitespace with [[:blank:]] in order to capture tabs and other space-like characters.

$ echo "  aaa bbb cc   " | sed -e 's/ *$//' -e 's/.* //'
cc
$ echo "  aaa bbb cc" | sed -e 's/ *$//' -e 's/.* //'
cc
$ echo "aaa bbb cc   " | sed -e 's/ *$//' -e 's/.* //'
cc
$ echo "aaa bbb cc" | sed -e 's/ *$//' -e 's/.* //'
cc
$ echo "  cc  " | sed -e 's/ *$//' -e 's/.* //'
cc
$ echo "cc" | sed -e 's/ *$//' -e 's/.* //'
cc
-1
cat file_name | rev | cut -f1 -d ' ' | rev
-1

Old thread but anyway, here's my 2 cents:

echo "This is a journey into South " |xargs|sed 's/.* //'
South

So: run through xargs to get rid of the trailing spaces (if any). Then remove everything up to and including the last space.

If you also want to ignore empty lines (not asked but your example suggests it), then add a delete of empty lines:

sed -e 's/.* //' -e '/^$/d'
2
  • 2
    Using xargs to remove trailing spaces is like using a hammer to kill a fly that landed on your foot.  The question shown an example input with multiple lines, and indicates that the OP wants the last word from each line — but xargs combines multiple input lines into one output line, so your answer yields only the last word of the last line.  And the 's/.* //' part has been given multiple times before, some including a solution for the space-at-the-end-of-the-line problem. Commented Jul 5, 2022 at 3:40
  • Aye you're right. Pasted a solution I used in a slightly different situation, didn't test it on mutliple lines.. On using xargs this way - I don't mind, it's short, fast and readable. Just not fit for purpose in this situation.. my bad.
    – joey
    Commented Jul 6, 2022 at 12:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .