0

I'm trying to use awk with the output of ss to print the fourth column. It works sometimes but other times it merges or splits the columns incorrectly. I've tried a few different options for FS, here it's two or more spaces because the field headers contain a single space.

This is giving me the fifth column and a blank header:

$ ss -tn
State   Recv-Q    Send-Q                Local Address:Port                   Peer Address:Port     
ESTAB   0         36                     172.31.19.34:22                   172.115.128.85:64478    
ESTAB   0         0             [::ffff:172.31.19.34]:80          [::ffff:172.115.128.85]:65446    


$ ss -tn | awk -F '[[:space:]][[:space:]]+' '{print $4}'

172.115.128.86:64478 
[::ffff:172.115.128.86]:65446 

The same command here is giving me the fourth column, this is what I want.

$ ss -tn
State     Recv-Q      Send-Q              Local Address:Port               Peer Address:Port       
ESTAB     0           36                   172.31.19.34:22               172.115.128.85:64478   

$ ss -tn | awk -F '[[:space:]][[:space:]]+' '{print $4}'
Local Address:Port
172.31.19.34:22

I know cut may be easier but I am using awk because I want to do further processing.

To add detail: I'm not sure why ss is showing this IPv6 style address. This is a connection from my laptop to the apache server, but my laptop doesn't have a IPv6 address.

  • 1
    Is it possible that ss is providing different outputs with and without a pipeline? Try ss | cat and compare with plain ss – muru May 30 at 6:22
  • Your 2nd attempt with awk -F '[[:space:]][[:space:]]+' works just fine. What's wrong? or may be you have a tab-character? could you do ss -tn | hexdump -c – Inian May 30 at 6:25
  • @Inian I'm not sure how to use hexdump but it does look like the first example has nothing between State and Recv, while the second example has 3 spaces. If I change FS to \t then it only makes one column – Elliott B May 30 at 6:57
  • 1
    At least on my system ss -tn's first line is inherently ambiguous, i.e. two fields are separated by a single <space> and a single space also appears as part of two fields. It cannot be split safely. You can inspect your lines with od. E.g. ss -tn | od -An -tx1 lets you spot tabs as 09 and spaces as 20. – fra-san May 30 at 7:51
  • 1
    @fra-san, I like the od -c invocation, as it shows characters in their "backslash" representation: \t, \r, etc – glenn jackman May 30 at 14:03
3

As muru hinted in a comment, awk is likely working consistently. What can vary is the spacing in the output of ss.

It turns out that ss -nt1 outputs seven columns, whose headers are: State, Recv-Q, Send-Q, Local Address, Port, Peer Address, Port. The fourth and fifth columns are separated by a colon (:); same thing for the sixth and seventh. All the others are separated by a space character.
All the columns are padded with spaces where required for alignment. The fourth and sixth are padded on their left, all the others on their right.

Further padding may happen:

  1. If the output of ss -nt is directed to a terminal:
    1. if the minimum length of its lines, computed as the sum of the longest content for each field plus the minimum spacing (six characters), is less than the width of the terminal, each line is expanded to the width of the terminal by evenly padding all the columns with spaces;
    2. otherwise lines are broken and fields are aligned across lines (padded as above, up to the width of the terminal).
  2. If the output of ss -nt is not directed to a terminal (e.g. it is piped or redirected to a regular file), the actual length of the lines is defined as the minimum multiple of 80 that is higher than the minimum length as defined above. All the columns are evenly padded with spaces to reach an overall line length that will consequently be of 80, 160, 240, ... characters2.

Thus, there is no guarantee that two columns will be separated by two or more spaces, making that sequence unreliable for splitting.

You can nevertheless handle the output of ss -tn in a reasonably safe way noting that the column headers are known and fixed and that, except for the headers, none of its columns should include spaces3:

ss -nt | sed '
  1 s/[ ]Address:/_Address|/g           # Remove the known spaces from column
                                        # headers; also, change ":" into "|"
  s/:\([^:|]*[ ]\)/|\1/g                # Change the colons used as separators
                                        # into vertical bars "|", to avoid
  s/:\([^:|]*\)$/|\1/g                  # confusion with those in IPv6s
' | awk -v FS='\\||[ ]+' -v OFS=":" '   # Split on sequences of one or more
  { print $4,$5 }                       # spaces OR on any vertical bar
'

This will print only the fourth and fifth columns (local address and port), separated by a colon. Note that, using a field separator that is not the default single space, awk will identify eight columns instead of seven and, if you do a { $1=$1; print; }, it will print an OFS at the end of any line for which the last column is right-padded with at least one space.


1 Other options (e.g. -i, -e, -m) drastically change the output of ss. For brevity and clarity we will only focus on this exact command.
2 Approximate and possibly inexact. But this is not relevant to the point of this question/answer.
3 Apparently this is not guaranteed, we deliberately won't try to cover all the less usual cases.

  • Thank you for the details. So it fails here when the line is >80 characters. I'm still trying to figure out how to reliably get the local port column. My goal is to detect when there are no active SSH sessions. – Elliott B Jun 2 at 1:41
  • @ElliottB Please, see my updated answer. Note that the first version was partly wrong. Still, splitting on occurrences of two or more space is unreliable because it fails for any line that has a length close to a multiple of 80. – fra-san Jun 2 at 9:32
2

Why isn't awk field separator working consistently?

It is, what is unreliable is the number of spaces in the output of ss.

the fourth column, this is what I want.

Then just remove the header (-H) and select the fourth column:

$ ss -taH | awk '{print $4}'
172.31.19.34:22
[::ffff:172.31.19.34]:80

As the header is fixed, just add it back (if needed):

$  echo "Local Address:Port"
Local Address:Port

Full command:

$ echo "Local Address:Port"; ss -tnH | awk '{print $4}'
Local Address:Port
172.31.19.34:22
[::ffff:172.31.19.34]:80

Yes, your computer always have IPv6 (one or several) addresses. If you don't want them, ask only for the IPv4 addresses:

$ ss -tnH4 | awk '{print $4}'
172.31.19.34:22
  • In my first example, removing the header does not help, like fra-san said because the line it's >80 characters. The -4 flag might work in this situation. – Elliott B Jun 3 at 15:44
  • 1
    Yes, it helps. It doesn't matter how many spaces are used to delimit fields, awk will process any run (one or more of spaces and/or tabs) as one delimiter. It doesn't matter that the line is >80 characters (which will only change the number of spaces used to delimit fields). In short, do try to process header fields with spaces at the same time as spaces as delimiters that is a sure source of problems, and, please, use the KISS approach (keep it simple), if possible. @ElliottB – Isaac Jun 3 at 16:33
  • Ok you're right, thank you – Elliott B Jun 3 at 17:58

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.