Home > OS >  Cut string in bash based on position of occurrence in first line
Cut string in bash based on position of occurrence in first line

Time:08-27

I'm trying to select specific columns (2 and 3) from command output, where the column separator is also present in the text. Consider this example text (fictional part of nmcli output):

           SSID BSSID             RSSI CHANNEL
      Something 12:34:56:78:98:ab -10  1
Something guest a2:34:56:78:98:ab -10  2
        Network b2:34:56:78:98:ab -20  3 
 Public network c2:34:56:78:98:ab -30  4

Columns are split by single spaces, but there are also single spaces in the SSIDs. Without those, it's an easy solution using cut.

In GNU sed, I somehow made it work using \w\s\w to replace spaces in SSIDs with underscores:

sed -e 's/\w\s\w/_/g'

But that doesn't work on the Mac. So my second best idea is splitting by position. Is it possible to:

  1. determine the position of BSSID, RSSI and CHANNEL strings in first line
  2. cut between BSSID_pos and RSSI_pos (1) and RSSI_pos - CHANNEL (2)

Is it there a smarter way? Those spaces in SSIDs also confuse awk, so

awk '{print $2 " " $3}'

doesn't work in those lines.

--- EDIT --- The problem is, there are also spaces in the last field when using on data produced by MacOS airport -s, so I can't go from back either:

                            SSID BSSID             RSSI CHANNEL HT CC SECURITY (auth/unicast/group)
                             AA1 d8:12:b6:b8:10:d5 -92  9       Y -- WPA(PSK/AES/AES) RSN(PSK/AES/AES)
                     XXXXX Guest ba:11:e4:4a:01:71 -90  6       Y -- RSN(PSK/AES/AES)
                         Netwrrk 5a:99:68:b7:0d:ca -89  36      Y DE RSN(PSK/AES/AES)
                      T-3_7edb87 64:11:ea:7e:db:87 -87  11      Y -- WPA(PSK/AES,TKIP/TKIP) RSN(PSK/AES,TKIP/TKIP)
                           abcde 58:22:8f:c6:7c:de -86  6       Y -- RSN(PSK/AES/AES)
                           abcde 64:66:88:3f:b2:9a -78  8       Y -- RSN(PSK/AES,TKIP/TKIP)
                           ababa 74:ac:77:b1:e3:59 -74  6       Y -- RSN(PSK/AES/AES)

Apologies for two questions in one; thank you all!

CodePudding user response:

Using any awk in any shell on every Unix box:

$ awk 'NR==1{ match($0,/BSSID.*RSSI/); next } { $0=substr($0,RSTART,RLENGTH); $1=$1; print }' file
d8:12:b6:b8:10:d5 -92
ba:11:e4:4a:01:71 -90
5a:99:68:b7:0d:ca -89
64:11:ea:7e:db:87 -87
58:22:8f:c6:7c:de -86
64:66:88:3f:b2:9a -78
74:ac:77:b1:e3:59 -74

CodePudding user response:

The following has been tested with GNU awk and the BSD awk that comes with macOS (but as Ed commented it should work with any POSIX awk):

$ awk 'NR==1 {n = match($0, /[[:space:]]BSSID[[:space:]]/)   1}
       NR>1  {$0 = substr($0, n); print $1, $2}' file.txt
12:34:56:78:98:ab -10
a2:34:56:78:98:ab -10
b2:34:56:78:98:ab -20
c2:34:56:78:98:ab -30 

The first block applies only on the first line and stores the index of BSSID in variable n. The second block applies to all other lines. It modifies them by suppressing the n-1 first characters, and prints the new first and second fields.

Just for fun (because it is a bit more complex), here is also a sed solution based on the same idea of parsing the header line, and tested with GNU sed and the BSD sed that comes with macOS.

$ sed -En '
1 {
  s/^(.*[[:space:]])BSSID[[:space:]].*/\1/
  h
}
2,$ {
  G
  :a
  s/.(.*\n.*)./\1/
  ta
  s/^([^[:space:]] [[:space:]] [^[:space:]] ).*/\1/
  p
}' file.txt
12:34:56:78:98:ab -10
a2:34:56:78:98:ab -10
b2:34:56:78:98:ab -20
c2:34:56:78:98:ab -30

We use extended regular expressions (-E) and suppress the default echoing (-n).

We first delete the end of the first line starting at BSSID (substitute command s/^(.*[[:space:]])BSSID[[:space:]].*/\1/) and store the result in the hold space (h). With your first example the hold space now contains (beginning and end marked with ^ and $):

^           SSID $

All lines except the first (2,$) are modified as follows, and printed (final p):

  • Append a newline followed by the hold space (G). The second line of your example becomes:

    ^      Something 12:34:56:78:98:ab -10  1\n           SSID $
    
  • Iterate (loop :a ... ta) by deleting the first character and the last character after the newline until all characters after the newline have been deleted (substitute command s/.(.*\n.*)./\1/). The second line of your example becomes:

    ^12:34:56:78:98:ab -10  1\n$
    
  • Delete everything after (and including) the second string of spaces (s/^([^[:space:]] [[:space:]] [^[:space:]] ).*/\1/). The second line of your example becomes:

    ^12:34:56:78:98:ab -10$
    

The GNU sed version is a bit more compact, if it matters:

$ sed -En '1{s/^(.*)\<BSSID\>.*/\1/;h;n}
    {G;:a;s/.(.*\n.*)./\1/;ta;s/^(\S \s \S ).*/\1/;p}' file.txt

CodePudding user response:

How about using GNU grep here(tested in RedHat), which will simply catch the needed string by using regex in it. Written and tested with shown samples Only.

Here is the Online demo for used regex.

grep -oP '(?<=\s)[0-9a-f]{2}(:[0-9a-f]{2}){5}\s -?[[:digit:]]{2}'  Input_file

OR in perl try following (thanks to @tripleee for this):

perl -nle 'm/(?<=\s)[0-9a-f]{2}(:[0-9a-f]{2}){5}\s -?[[:digit:]]{2}/ and print "$&"'  Input_file

CodePudding user response:

This is why many Unix tools prefer to put the variable-length fields last (compare ls -l) but as long as the tail has predictable formatting, just pick out the things counting from the end.

sed '2,$s/^.* \([0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f] *[-0-9]*\) [0-9]*$/\1/'

or

awk 'FNR>1{ print $(NF-2) " " $(NF-1) }'

A better solution altogether is to read the manual page and search for an option to print only the information you need.

nmcli -t -f BSSID,RSSI

(I can't run nmcli inside Docker, it seems, so no way to test this; but I trust you can take it from here even if the command line doesn't directly work.)

CodePudding user response:

I would harness substr string function of GNU AWK following way, let file.txt content be

           SSID BSSID             RSSI CHANNEL
      Something 12:34:56:78:98:ab -10  1
Something guest a2:34:56:78:98:ab -10  2
        Network b2:34:56:78:98:ab -20  3 
 Public network c2:34:56:78:98:ab -30  4

then

awk '{print substr($0,17,22)}' file.txt

gives output

BSSID             RSSI
12:34:56:78:98:ab -10 
a2:34:56:78:98:ab -10 
b2:34:56:78:98:ab -20 
c2:34:56:78:98:ab -30 

Explanation: I print substring starting at 17th character, which is 22 characters long.

(tested in GNU Awk 5.0.1)

  • Related