4

I'm working on a one-liner to get the PCI Address of GPUs from Incus. Here is the output of the first part of the command:

incus info --resources | grep -E 'GPU:|GPUs:' -A 20
GPUs:
  Card 0:
    NUMA node: 0
    Vendor: ASPEED Technology, Inc. (1a03)
    Product: ASPEED Graphics Family (2000)
    PCI address: 0000:22:00.0
    Driver: ast (6.12.9-production+truenas)
    DRM:
      ID: 0
      Card: card0 (226:0)
      Control: controlD64 (226:0)
  Card 1:
    NUMA node: 0
    Vendor: NVIDIA Corporation (10de)
    Product: GP102 [GeForce GTX 1080 Ti] (1b06)
    PCI address: 0000:2b:00.0
    Driver: nvidia (550.142)
    DRM:
      ID: 1
      Card: card1 (226:1)
      Render: renderD128 (226:128)
    NVIDIA information:
      Architecture: 6.1
      Brand: GeForce
      Model: NVIDIA GeForce GTX 1080 Ti
      CUDA Version: 12.4
      NVRM Version: 550.142
      UUID: GPU-9d45b825-9a28-afab-1189-e071779f7469

I'm using grep to limit it to 'amd|intel|nvidia', awk to print the PCI Address:, then sed to remove the whitespaces. I keep getting a trailing hash (#) char which is actually generated from the printf. printf is removing all the additional newlines automatically for me. However, I haven't found a way to nix the hash char. What am I missing here? If there is a better way to do this I'm open to that as well. Thank you!

incus info --resources | grep -E 'GPU:|GPUs:' -A 20 | grep -Ei 'amd|intel|nvidia' -A 20 | awk -F "PCI address:" '{printf $2}' | sed 's/ //'
0000:2b:00.0#

Edit: In short and to add some clarity, I just need to grab the PCI Address: from one of amd, intel, or nvidia and output the PCI Address: only.

7
  • 1
    what are you expecting 'amd|intel|nvidia' to match on? Vendor:? Driver:? NVIDIA information:? Model:? if we could get the specific line(s) you're looking to match on then there are a few workable options; fwiw, once you pull awk into the mix there's rarely a need for grep or sed, but in this case we'll need to know what lines/labels you're looking to match on
    – markp-fuso
    Commented Feb 25 at 19:46
  • @Cyrus you must be using a newer version of Incus. Not available on 6.0.3 which is what is currently in Debian Bookworm. Commented Feb 25 at 19:50
  • 1
    I don't know incus, nor the actual structure of its --format=json output. But if your version lacks that feature, you could (at least based on the sample output provided) try to interpret it as YAML, and use yq instead of jq. Something along the lines of incus … | yq -r '.[][] | select(.Vendor | contains("AMD", "INTEL", "NVIDIA")) | .["PCI address"]' would work with both kislyuk/yq and mikefarah/yq.
    – pmf
    Commented Feb 25 at 20:08
  • 1
    @dasunsrule32 mikefarah/yq needs no installation, it provides precompiled executables for download (see assets). As for kislyuk/yq, you could simply run it in a Python venv. It does need jq, though, but this is also directly available as executable binary for download, so, again, no installation needed.
    – pmf
    Commented Feb 25 at 20:20
  • 1
    @dasunsrule32: I read the wrong page in the documentary.
    – Cyrus
    Commented Feb 25 at 20:45

7 Answers 7

4

This might be what you're trying to do, using any POSIX awk:

$ awk '
    /^[^[:space:]]/ { g = (/GPUs?:/) }
    /Vendor:/ { v = (tolower($0) ~ /amd|intel|nvidia/) }
    g && v && sub(/.*PCI address: /,"")
' file
0000:2b:00.0

You don't need the | grep -E 'GPU:|GPUs:' -A 20, nor any call to sed or any other tool. If the above doesn't do exactly what you want then edit your question to clarify your requirements and/or provide more truly representative sample input/output.

By the way, regarding printf $2 from the OPs code in the question - only do printf $2 (for any input string) if you have a VERY specific and highly unusual need to interpret input as printf formatting strings, otherwise use printf "%s", $2 so it won't fail when the input contains printf formatting strings like %s.

3
  • This works pretty well and I think this will get it done. I'm not that great at awk, so... What was I doing wrong that I kept getting the # in my output previously? I tried running printf with %s before and it did not work as I thought it would. Thank you. Commented Feb 25 at 22:12
  • I'd guess there was some hidden character in your input that was being interpreted as # when used in the formatting string but idk, I can't reproduce it from the sample input in the question.
    – Ed Morton
    Commented Feb 25 at 22:14
  • 1
    Makes sense, thank you! I linked to this thread where I needed help to credit you. :) Commented Feb 25 at 22:17
3

This might work for you (GNU sed):

sed -nE '/^\S/{h;d}
         x;/^GPUs?:/ba;x;d
         :a;x;/^\s*Vendor:/{x;s/\n.*//;x;H;d}
         x;/^GPUs?:\n\s*Vendor:.*(amd|intel|nvidia)/Ibb;x;d
         :b;x;s/.*PCI address: //p' file

If a line contains PCI address: print the remainder of that line if and only if the lines preceding it also contained the string GPUs: and Vendor: with the vendor being either amd, intel or nvidia.

An alternative:

sed -En ':a;/^GPUs?/!b
         :b;n;/^\S/ba;/^\s*Vendor:/!bb
         :c;/amd|intel|nvidia/I!bb
         :d;n;/^\S/ba;/^\s*Vendor:/bc
         s/.*PCI address: //p;tb;bd' file 
1
  • This works quite well too! Commented Feb 26 at 16:31
2

To simulate OP's incus output while also adding a few lines at the start/end:

$ cat incus.out
Not this section:
    Vendor: Intel
    PCI Address: not this address
GPUs:
  Card 0:
    NUMA node: 0
    Vendor: ASPEED Technology, Inc. (1a03)
    Product: ASPEED Graphics Family (2000)
    PCI address: 0000:22:00.0
    Driver: ast (6.12.9-production+truenas)
    DRM:
      ID: 0
      Card: card0 (226:0)
      Control: controlD64 (226:0)
  Card 1:
    NUMA node: 0
    Vendor: NVIDIA Corporation (10de)
    Product: GP102 [GeForce GTX 1080 Ti] (1b06)
    PCI address: 0000:2b:00.0
    Driver: nvidia (550.142)
    DRM:
      ID: 1
      Card: card1 (226:1)
      Render: renderD128 (226:128)
    NVIDIA information:
      Architecture: 6.1
      Brand: GeForce
      Model: NVIDIA GeForce GTX 1080 Ti
      CUDA Version: 12.4
      NVRM Version: 550.142
      UUID: GPU-9d45b825-9a28-afab-1189-e071779f7469
Not this section:
    Vendor: AMD
    PCI Address: again, not this address

One awk idea:

$ cat incus.awk
BEGIN          { n=split(inlist,arr,",")            # split input variable "inlist" on commas and store in array arr[]
                 for (i=1;i<=n;i++)                 # loop through array to build ...
                     vendors[tolower(arr[i])]       # associative array of lowercase vendors
               }

gflag &&                                            # if gpu flag is set and ...
vflag &&                                            # if vendor flag is set and ...
/PCI address:/ { print $NF; gflag = vflag = 0 }     # line contains string "PCI address:" then print last field and clear flags

gflag &&                                            # if gpu flag is set and ...
/Vendor:/      { for (vendor in vendors)            # line contains string "Vendor:" then loop through vendors ...
                     if (tolower($0) ~ vendor)      # looking for match and if we find one then ...
                        vflag = 1                   # set vendor flag
               }

/GPUs:/        { gflag = 1; vflag = 0 }             # if line contains string "GPUs:" then set gpu flag and clear vendor flag

NOTE: this script relies on operator providing a comma delimited list of vendors in the awk variable inlist

Taking for a test drive:

$ cat incus.out | awk -v inlist='amd,intel,nvidia' -f incus.awk
0000:2b:00.0
0

Try this piece of code, which uses the Record Range pattern.

incus info --resources | awk '/^GPUs:/,!/^GPUs:|[^[:blank:]]/ { if(/Vendor/ && (tolower($0)~/amd|intel|nvidia/)) vendor = 1; if(/PCI address:/ && vendor) { printf $3; vendor=0 } }'

Note: if there are two GPUs from either amd, nvidia or intel, printf will print both results concatenated.

1
  • This works, but outputs the trailing #. incus info --resources | awk '/^GPUs:/,!/^GPUs:|[^[:blank:]]/ { if(/Vendor/ && (tolower($0)~/amd|intel|nvidia/)) vendor = 1; if(/PCI address:/ && vendor) { printf $3; vendor=0 } }' 0000:2b:00.0# Commented Feb 26 at 16:27
0

printf is removing all the additional newlines automatically for me.

No, printf does not do that, newlines are removed when GNU AWK does splits file into records (assuming default RS, which you are using). One might check that using index string function

echo 'PCI address: some' | awk -F "PCI address:" '{print index($2,"\n")}'

gives output

0

which means not found, that is $2 does not contain any newline before you ram into printf.

awk -F "PCI address:" '{printf $2}'

Note that if you use printf this way you MUST guarantee than there never be any formatting string inside $2, otherwise that code will malfunction for example

echo 'PCI address: %d' | awk -F "PCI address:" '{printf $2}'

will result in fatal: not enough arguments to satisfy format string to avoid that problem set ORS (output row separator) to empty string and use print that is

echo 'PCI address: %d' | awk -F "PCI address:" 'BEGIN{ORS=""}{print $2}'

then sed to remove the whitespaces

This is not accurate description of what sed 's/ //' is doing. It does remove first SPACE character from each line, if there is one, otherwise do nothing. If you wish to instruct GNU sed to remove all whitespace characters from each line do

command | sed 's/[[:space:]]//g'

where [:space:] is named character class and g informs GNU sed to make change globally

haven't found a way to nix the hash char

This is task for tr command

echo "0000:2b:00.0#" | tr -d '#'

gives output

0000:2b:00.0

Note that tr from GNU coreutils does support character classes so you might use it in place of sed command given above, if you have GNU coreutils' tr, as follows

command | tr -d '[:space:]'

If there is a better way to do this I'm open to that as well

Most often if you have pipeline consisting of awk and grep and sed, you should be able to replace it with single awk. I would not do that as others already furnished awk-only solutions.

1
  • This is interesting, but when using print vs printf in awk, print was generating newlines, however, printf would remove all newlines but append the #. I tried tr as well and that did not remove that trailing #. TrueNAS SCALE uses zsh by default. I'm thinking that might have something to do with it... echo $SHELL /usr/bin/zsh Commented Feb 26 at 16:26
0

From here on, once vendor is matched, extraction of PCI address field is trivial :

incus info —resources |

awk '1 < NF' FS='Us?:$|^[ \t]+(Vendor|PCI address): '

GPUs:
    Vendor: ASPEED Technology, Inc. (1a03)
    PCI address: 0000:22:00.0
    Vendor: NVIDIA Corporation (10de)
    PCI address: 0000:2b:00.0
0

Given that PCI address: and driver: lines go one by one, with Raku/Sparrow this is just few lines of code:

begin:
  ~regexp: "PCI address:" \s+ (\S+)
  ~regexp: "Driver:" \s+ [nvidia || amd || intel]
end:

code: <<RAKU
!raku
for streams().values -> $pci {
   say $pci.head<captures>.head
}
RAKU 

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.