0

Can anybody help me please?

I am trying to print output of a command output for a bash script to check the SuSE product registration

command output (/usr/bin/SUSEConnect --status-text)

Installed Products:
------------------------------------------

  Desktop Applications Module
  (sle-module-desktop-applications/15.2/x86_64)

  Registered

------------------------------------------


  Server Applications Module
  (sle-module-server-applications/15.2/x86_64)

  Registered

------------------------------------------

  Basesystem Module
  (sle-module-basesystem/15.2/x86_64)

  Registered

------------------------------------------

  Legacy Module
  (sle-module-legacy/15.2/x86_64)

  Not Registered

------------------------------------------

I just filtered it

sudo /usr/bin/SUSEConnect --status-text|sed -r '/^\s*$/d'|tr -d '[:blank:]'|grep -Ev "^-|InstalledProducts"
DesktopApplicationsModule
(sle-module-desktop-applications/15.2/x86_64)
Registered
ServerApplicationsModule
(sle-module-server-applications/15.2/x86_64)
Registered
BasesystemModule
(sle-module-basesystem/15.2/x86_64)
Registered
LegacyModule
(sle-module-legacy/15.2/x86_64)
NotRegistered

But I want to remove the lines (*) and print like

Installed Products :  DesktopApplicationsModule Registered
                      ServerApplicationsModule  Registered
                      BasesystemModule          Registered
                      LegacyModule              NotRegistered   

Thanks,

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
vijayedm
  • 15
  • 3

4 Answers4

2

Using any POSIX awk plus column:

$ cat tst.awk
BEGIN { OFS="\t" }
!NF { next }
{ gsub(/^[[:space:]]+|[[:space:]]+$/,"") }
NR==1 { vals[1,1] = $0; next }
/^-+$/ { ++numRows; colNr=1; next }
{
    vals[numRows,++colNr] = $0
    numCols = colNr
}
END {
    numRows -= (colNr == 1)
    for ( rowNr=1; rowNr<=numRows; rowNr++) {
        for ( colNr=1; colNr<=numCols; colNr++ ) {
            printf "%s%s", vals[rowNr,colNr], (colNr<numCols ? OFS : ORS)
        }
    }
}

$ awk -f tst.awk file | column -s $'\t' -t
Installed Products:  Desktop Applications Module  (sle-module-desktop-applications/15.2/x86_64)  Registered
                     Server Applications Module   (sle-module-server-applications/15.2/x86_64)   Registered
                     Basesystem Module            (sle-module-basesystem/15.2/x86_64)            Registered
                     Legacy Module                (sle-module-legacy/15.2/x86_64)                Not Registered

You can choose to only print some columns instead of all of them if you prefer, e.g.:

    for ( rowNr=1; rowNr<=numRows; rowNr++) {
        print vals[rowNr,1], vals[rowNr,2], vals[rowNr,4]
    }
Ed Morton
  • 188,023
  • 17
  • 78
  • 185
1

The following should work with any POSIX awk (tested with GNU awk and the BSD awk that comes with macOS):

$ awk -v RS='' -v h='Installed Products:' '
  {gsub(/^[[:space:]]*|[[:space:]]*(\n.*)?$/,"")}
  NR%3==2 {p=$0} NR%3==0 {printf("%-25s%-35s%s\n", h, p, $0); h=""}' file
Installed Products:      Desktop Applications Module        Registered
                         Server Applications Module         Registered
                         Basesystem Module                  Registered
                         Legacy Module                      Not Registered

Adapt the output field widths (25, 35) to your actual data.

Explanations: with RS set to the null string the record separator is one or more blank lines. All records are preprocessed (gsub(/^[[:space:]]*|[[:space:]]*(\n.*)?$/,"")) to keep only the first line with leading and trailing spaces removed. For records 2, 5, 8... (the application name) we store the record in variable p. For records 3, 6, 9... (the registration status) we print the output for the current application:

  • the header stored in variable h, left-aligned on 25 characters,
  • the application name stored in variable p, left-aligned on 35 characters,
  • the registration status, that is, the current record ($0).

After the first printf we clear the header (h="").

If you prefer to automatically compute the output field widths we must wait until the end of the input before we can print because we need to know the longest application name:

$ awk -v RS='' -v h='Installed Products:' '
  {gsub(/^[[:space:]]*|[[:space:]]*(\n.*)?$/,"")}
  NR%3==2 {p[++n]=$0; mp=length>mp?length:mp} NR%3==0 {q[n]=$0}
  END {mh=length(h)
       for(i=1;i<=n;i++)
         printf("%-*s%-*s%s\n", mh+1, i==1?h:"", mp+1, p[i], q[i])}' file
Installed Products: Desktop Applications Module Registered
                    Server Applications Module  Registered
                    Basesystem Module           Registered
                    Legacy Module               Not Registered

Note: as other answers suggest we could also use the column utility for the final formatting but we would then need to chose a field separator for the input of column that cannot be found in your primary input. Unless you know such a separator and are 100% sure it cannot be found in your primary input, doing the whole stuff with just awk is maybe a bit safer.

Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
0

I've copied your command output to a file 'f' to make some tests, you don't need tr and grep, sed alone can remove all unnecessary stuff:

$ sed -r '/^\s*$|^-|\(.*\)|^Ins/d;s/ //g' f
DesktopApplicationsModule
Registered
ServerApplicationsModule
Registered
BasesystemModule
Registered
LegacyModule
NotRegistered

Now to pretty print this lets use printf:

$ printf '%-30s%s\n' $(sed -r '/^\s*$|^-|\(.*\)|^Ins/d;s/ //g' f)
DesktopApplicationsModule     Registered
ServerApplicationsModule      Registered
BasesystemModule              Registered
LegacyModule                  NotRegistered

Looks cool) and to make it the way you wanted lets do this:

# add all data to an array
arr=( $(sed -r '/^\s*$|^-|\(.*\)|^Ins/d;s/ //g' f) )

# print initial text and first 2 items from array
printf 'Installed Products : %-30s%s\n' ${arr[@]:0:2}

# print the rest, loop over the rest of array
for ((i=2; i<${#arr[@]}; i+=2)); {
    # empty valued added here⤵ to propper alighnment
    printf '%21s%-30s%s\n'   ''   ${arr[@]:i:2}
}

Result:

Installed Products : DesktopApplicationsModule     Registered
                     ServerApplicationsModule      Registered
                     BasesystemModule              Registered
                     LegacyModule                  NotRegistered

Update, here is the sed rule /^\s*$|^-|\(.*\)|^Ins/d;s/ //g explanation. There are 2 sed commands separated via ';'. First /.../d - delete command and second s///g - substitute command. Lets take a closer look at them:

  1. /^\s*$|^-|\(.*\)|^Ins/d this command is consists of several regex patterns to match unwanted strings, all matched strings would be removed:
  • ^\s*$ - will match strings of 'space' characters
  • ^- - will match strings starting from '-'
  • \(.*\) - will match strings with '()'
  • ^Ins - will match strings starting with 'Ins'
  1. s/ //g substitute all spaces with nothing(remove spaces) I've added this coz OP has the desired output in description without spaces.

But according to the OP's comment spaces must be preserved, lets do this. Looks like we only need to remove 2-nd sed command s/ //g? Nope this will ruin the output:

Installed Products : Desktop                       Applications
                     Module                        Registered
                     Server                        Applications
                     Module                        Registered
                     Basesystem                    Module
                     Registered                    Legacy
                     Module                        Not
                     Registered

Instead we need to change(substitute) it to some pattern for example _SPS_ to create popper amount of items in array and then change it back to spaces. The script will be like this:

# add all data to an array
arr=( $(sed -r '/^\s*$|^-|\(.*\)|^Ins/d;s/ /_SPS_/g' f) )
arr=( "${arr[@]//_SPS_/' '}" ) # change _SPS_ back to space in whole array

# print initial text and first 2 item from array
printf 'Installed Products : %-30s%s\n' "${arr[@]:0:2}"

# print the rest, loop over the rest of array
for ((i=2;i<${#arr[@]}; i+=2)); {
    # empty valued added here to propper alighnment
    printf '%21s%-30s%s\n' '' "${arr[@]:i:2}"
}

Notice that now I'm wrapping ${arr[@]:i:2} in " this is essential coz now we've got items with spaces. And here is the output:

Installed Products :   Desktop Applications Module   Registered
                       Server Applications Module    Registered
                       Basesystem Module             Registered
                       Legacy Module                 Not Registered
Ivan
  • 6,188
  • 1
  • 16
  • 23
  • Hello, Thank you, it worked, but small issue, it remove all the space in between "SUSELinuxEnterpriseServerforSAPApplications15SP2 Registered" ` Installed Products :` `DesktopApplicationsModule Registered` `SUSELinuxEnterpriseServerforSAPApplications15SP2 Registered` `SUSELinuxEnterpriseHighAvailabilityExtension15SP2 Registered` could you please explain `sed -r '/^\s*$|^-|\(.*\)|^Ins/d;s/ //g'` – vijayedm Jul 14 '23 at 08:54
  • @vijayedm Hi, I've updated my answer, please take a look. – Ivan Jul 14 '23 at 10:09
  • 1
    Don't use a string like `_SPS_` that you hope won't occur in the input - to read the output of a command into an array when that output can contain spaces is `IFS=$'\n' read -r -d '' -a arr < <( sed '...' && printf '\0' )` where your sed command would use newlines to separate the fields, see [reading-output-of-a-command-into-an-array-in-bash](https://stackoverflow.com/a/32931403/1745001). – Ed Morton Jul 15 '23 at 10:53
0

Shorten your pipeline with a little more logic in your sed.
You don't need separate calls to tr and grep.

sudo /usr/bin/SUSEConnect --status-text |
  sed -n '/Installed Products:/h; /Module$/H; 
          /Registered/{ H; g; s/ //g; s/\n/\t/g; p; z; x; }' |
  column --table --separator $'\t' 

or, collapsed to a denser single line,

sudo /usr/bin/SUSEConnect --status-text|sed -n '/Installed Products:/h; /Module$/H; /Registered/{H;g;s/ //g;s/\n/\t/g;p;z;x;}'|column -ts $'\t' 

To see it in action I just manually generated the output from your example:

$: echo "
Installed Products:
------------------------------------------

  Desktop Applications Module
  (sle-module-desktop-applications/15.2/x86_64)

  Registered

------------------------------------------


  Server Applications Module
  (sle-module-server-applications/15.2/x86_64)

  Registered

------------------------------------------

  Basesystem Module
  (sle-module-basesystem/15.2/x86_64)

  Registered

------------------------------------------

  Legacy Module
  (sle-module-legacy/15.2/x86_64)

  Not Registered

" | sed -n '/Installed Products:/h; /Module$/H; 
            /Registered/{ H; g; s/ //g; s/\n/\t/g; p; z; x; }' |
    column --table --separator $'\t' 
InstalledProducts:  DesktopApplicationsModule  Registered
                    ServerApplicationsModule   Registered
                    BasesystemModule           Registered
                    LegacyModule               NotRegistered

Broken out a little -

-n don't print unless I say so.
/Installed Products:/h put this line in the hold buffer (wipes any previous content)
/Module$/H add this line to the Hold buffer
/Registered/{ open a block of commands on this line:
H add to the Hold buffer
g replace the pattern buffer with the hold buffer contents
s/ //g remove all spaces
s/\n/\t/g replace newlines with tabs
p print the edited pattern buffer
z zap (empty) the pattern buffer - GNU specific
x exchange the pattern and hold buffers (empties the hold) }' close the code block

If not using GNU sed, replace the z with s/.//g, and you might need all these commands on different lines instead of just separated with semicolons.

If you wanted to include the names, add -E and change /Module$/H to /Module$|[(]/H -

sudo /usr/bin/SUSEConnect --status-text | sed -En '
  /Installed Products:/h; /Module$|[(]/H;
  /Registered/{ H; g; s/ //g; s/\n/\t/g; p; s/.//g; x; }
' | column --table --separator $'\t'
InstalledProducts:  DesktopApplicationsModule  (sle-module-desktop-applications/15.2/x86_64)  Registered
                    ServerApplicationsModule   (sle-module-server-applications/15.2/x86_64)   Registered
                    BasesystemModule           (sle-module-basesystem/15.2/x86_64)            Registered
                    LegacyModule               (sle-module-legacy/15.2/x86_64)                NotRegistered

As Renaud pointed out, using column does require you use a field separator not in your input. While playing with this I made it work with a carriage return (\r) and a bell character (\a). YMMV.

Paul Hodges
  • 13,382
  • 1
  • 17
  • 36