Reputation: 115
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.
Upvotes: 4
Views: 104
Reputation: 58518
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
Upvotes: 2
Reputation: 36700
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.
Upvotes: 0
Reputation: 214
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.
Upvotes: 0
Reputation: 204381
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
.
Upvotes: 3
Reputation: 35256
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
Upvotes: 2