131

I have this input, which is displayed in columns. I would like to get the last column of this sample:

[  3]  1.0- 2.0 sec  1.00 MBytes  8.39 Mbits/sec
[  3]  2.0- 3.0 sec   768 KBytes  6.29 Mbits/sec
[  3]  3.0- 4.0 sec   512 KBytes  4.19 Mbits/sec
[  3]  4.0- 5.0 sec   256 KBytes  2.10 Mbits/sec
...

If I use

cut -d\  -f 13

I get

Mbits/sec
6.29
4.19
2.10

because sometimes there are additional spaces in between.

4
  • 1
    The last column is Mbits/sec, is that what you want or the 2 last columns?
    – terdon
    Jan 18, 2014 at 14:43
  • 1
    I only want to get the 2nd last column, only the numbers
    – rubo77
    Aug 11, 2015 at 0:37
  • I was looking for the same thing for wg show all latest-handshakes which looks like multiple whitespace for separator (I don't really know) and it turns out that the default separator (whatever that is) worked fine! So <cmd> | cut -f 3 worked nicely.
    – Karl Pokus
    Mar 24, 2021 at 12:24
  • See stackoverflow.com/questions/7142735/…
    – rogerdpack
    Oct 29, 2021 at 18:43

7 Answers 7

196

If we use tr command along with squeeze option (-s flag ) to convert all multiple consecutive spaces to a single space and then perform cut operation with space as delimiter – we can access the required column carrying the numbers:

tr -s ' ' file | cut -d ' ' -f 8
5
  • 7
    BSD cut offers -w for such cases!
    – Michael-O
    Mar 24, 2020 at 10:05
  • @Michael-O Nice! I'm using cut from GNU coreutils on Arch Linux and it lacks such an option.
    – mehdix
    May 26, 2020 at 12:40
  • 3
    this can be done without cat: tr -s ' ' file | cut -d ' ' -f 8
    – Josh
    Aug 26, 2020 at 14:16
  • 1
    My version of tr (8.30) does not accept a file, so I need to redirect the stream. The command becomes: tr -s ' ' < file | cut -d ' ' -f 8 (mind the <). Feb 15 at 20:23
  • @AnthonyLabarre Indeed. I tried to edit the post to make that correction, but SO said "Edits must be at least 6 characters; is there something else to improve in this post?", and I refuse to play games to get around that, so the wrong answer stands, at least for now. Apr 12 at 15:31
29

To answer your question literally:

sed 's/   */:/g' | cut -d : -f 5

or

awk -F '  +' '{print $5}'

But that won't do if the number in brackets reaches 10, etc. If you're only interested in the numbers, you could remove everything else.

sed 's/[^.0-9][^.0-9]*/:/g' | cut -d : -f 6
6
  • yes, sure only the numbers, but only your 3rd example works correctly
    – rubo77
    Jan 18, 2014 at 0:08
  • @rubo77 Works for me. The first two examples do exactly what you ask in your title. Or did you want to strip off the unit as well? In that case, add | sed 's/ .*//' at the end of the first two examples. Of course there are many other ways to do it. Jan 18, 2014 at 0:13
  • a bit shorter with + instead of *: cat test | sed 's/[^.0-9]\+/:/g' | cut -d : -f 6
    – rubo77
    Sep 1, 2016 at 8:29
  • @rubo77 If your sed supports it, that is. It's supported by GNU and BusyBox but not by e.g. BSD or Solaris. POSIX specifies + and ? in ERE but leaves \+ and \? in BRE undefined. Sep 1, 2016 at 8:57
  • So the right option for columned input into cut would be -c35-38
    – rubo77
    Jun 20, 2020 at 2:33
10

These commands will all print the last column of a space separated file:

  • awk '{print $NF}' file

    in awk, NF is the number of fields and $NF is the last field.

  • perl -lane 'print $F[$#F]' file

    -a splits the file on whitespace into the array @F, $#F is the number of elements in the array so $F[$#F] is the last element. The -n means read the file given on the command line and apply the script passed with -e to each line. -l just adds a newline character (\n) to each print statement.

  • sed 's/.* //g'

    a simple regular expression that matches everything to the last space and deletes it, leaving only the last column.

  • rev file | cut -d' ' -f 1 | rev

    rev reverses its output so the last field is the first, cut with delimiter space to print it and rev to reverse the text back to normal. This won' t work if you have consecutive whitespace.

Based on your input, I am guessing you don't actually want the last column but the penultimate one or the two last ones. In that case use these to print the last 2 (8.39 Mbits/sec):

awk '{print $(NF-1),$NF}' file 
perl -lane 'print "$F[$#F-1] $F[$#F]"' file 
sed 's/.* \(.* .*\)/\1/' file 
rev file | cut -d' ' -f 1,2 | rev

and these to print the penultimate (8.39):

awk '{print $(NF-1)}' file 
perl -lane 'print $F[$#F-1]' file 
sed 's/.* \(.*\) .*/\1/' file 
rev file | cut -d' ' -f 2 | rev
5

You can't separate multiple occurrence of whitespaces using cut as per manual:

Output fields are separated by a single occurrence of the field delimiter character.

unless the text is separated by the same amount or you use tr to remove excess of them.

Otherwise use alternative tools such as awk, sed or ex.

For example:

ex -s +'%norm $2Bd0' +%p +q! foo.txt

Replace +q! with -cwq to save the changes in-place.

0
3

the right option for columned input into cut would be

cut -c35-38
0
2

Use a perl one-liner like so:

perl -lane 'print $F[-2]' input_file

Explanation:

Option -e causes the perl interpreter to look for the script inline, rather than in a file.

Option -n causes the input (file or STDIN from a pipe) to be read line by line.

Option -l strips the input record separator (OS-dependent, newline on UNIX by default) after reading the line, and adds it at the end to every print

Option -a causes each input line to be split on whitespace into array @F, and $F[-2] is the second element counting from the end, which is the field you want. You can also use $F[$#F-1], where $#F is the last index of the array @F, which is slightly less readable.

0

Using Raku (formerly known as Perl_6)

raku -ne '.words[*-2..*].put;' 

Sample Input:

[  3]  1.0- 2.0 sec  1.00 MBytes  8.39 Mbits/sec
[  3]  2.0- 3.0 sec   768 KBytes  6.29 Mbits/sec
[  3]  3.0- 4.0 sec   512 KBytes  4.19 Mbits/sec
[  3]  4.0- 5.0 sec   256 KBytes  2.10 Mbits/sec

Sample Output:

8.39 Mbits/sec
6.29 Mbits/sec
4.19 Mbits/sec
2.10 Mbits/sec

You might want to try Raku, a member of the Perl-family of programming languages. Above, the words routine breaks on whitespace. Columns can be selected with square brackets: since Raku (and Perl) are zero-indexed, the second-to-last column is *-2 and the last column is *-1. Here, either words[*-2..*-1] or words[*-2..*] works, the latter indicating 'give me the second-to-last column up to * whatever'.

Oh, the OP only wants the second-to-last column? Titled by the last column?

~$ perl6 -ne '.words[*-1].put if ++$ == 1; .words[*-2].put;' file
Mbits/sec
8.39
6.29
4.19
2.10

https://docs.raku.org/routine/words
https://raku.org

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.