2

I was surprised that I couldn't find this question on here.

I would like to take extract one line from a telnet response and make it a variable. (actually one number from that line). I can extract up to where I need using telnet.read_until(), but the whole beginning is still there. The printout shows different statuses of a machine.

The line I am trying to get is formatted like this:

CPU Utilization          : 5 %

I really only need the number, but there are many ':' and '%' characters in the rest of the output. Can anyone help me extract this value? Thanks in advance!

Here is my code (this reads the whole output and prints):

import telnetlib, time


print ("Starting Client...")
host    = input("Enter IP Address: ")
timeout = 120

print ("Connecting...")
try:
    session = telnetlib.Telnet(host, 23, timeout)
except socket.timeout:
    print ("socket timeout")
else:
    print("Sending Commands...")
    session.write("command".encode('ascii') + b"\r")
    print("Reading...")
    output = session.read_until(b"/r/n/r/n#>", timeout )
    session.close()
    print(output)
    print("Done")

Edit: some example of what an output could be:

Boot Version         : 1.1.3 (release_82001975_C)
Post Version         : 1.1.3 (release_82001753_E)
Product VPD Version  : release_82001754_C
Product ID           : 0x0076
Hardware Strapping   : 0x004C
CPU Utilization      : 5 %
Uptime               : 185 days, 20 hours, 31 minutes, 29 seconds
Current Date/Time    : Fri Apr 26 17:50:30 2013
mad5245
  • 394
  • 3
  • 8
  • 20

4 Answers4

6

As you say in the question:

I can extract up to where I need using telnet.read_until(), but the whole beginning is still there.

So you can get all of the lines up to and including the one you want into a variable output. The only thing you're missing is how to get just the last line in that output string, right?

That's easy: just split output into lines and take the last one:

output.splitlines()[:-1]

Or just split off the last line:

output.rpartition('\n')[-1]

This doesn't change output, it's just an expression that computes a new value (the last line in output). So, just doing this, followed by print(output), won't do anything visibly useful.

Let's take a simpler example:

a = 3
a + 1
print(a)

That's obviously going to print 3. If you want to print 4, you need something like this:

a = 3
b = a + 1
print(b)

So, going back to the real example, what you want is probably something like this:

line = output.rpartition('\n')[-1]
print(line)

And now you'll see this:

CPU Utilization          : 5 %

Of course, you still need something like Johnny's code to extract the number from the rest of the line:

numbers = [int(s) for s in line.split() if s.isdigit()]
print(numbers)

Now you'll get this:

['5']

Notice that gives you a list of one string. If you want just the one string, you still have another step:

number = numbers[0]
print(number)

Which gives you:

5

And finally, number is still the string '5', not the integer 5. If you want that, replace that last bit with:

number = int(numbers[0])
print(number)

This will still print out 5, but now you have a variable you can actually use as a number:

print(number / 100.0) # convert percent to decimal

I'm depending on the fact that telnet defines end-of-line as \r\n, and any not-quite-telnet-compatible server that gets it wrong is almost certainly going to use either Windows-style (also \r\n) or Unix-style (just \n) line endings. So, splitting on \n will always get the last line, even for screwy servers. If you don't need to worry about that extra robustness, you can split on \r\n instead of \n.


There are other ways you could solve this. I would probably either use something like session.expect([r'CPU Utilization\s*: (\d+)\s*%']), or wrap the session as an iterator of lines (like a file) and then just do write the standard itertools solution. But this seems to be simplest given what you already have.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • I actually want a line in the middle of the output. And it is not in a static position (could be 6th line could be 10th...) Any ideas? – mad5245 Apr 26 '13 at 19:51
  • 1
    @mad5245: Yes, but you already know how to get all the lines up to and including the one you want with `read_until`, right? So, to get the last of those lines (which is the one you want), just do the `read_until`, and then take the last line as shown above. – abarnert Apr 26 '13 at 19:54
  • Well that makes me feel dumb haha, I am having an issue with the rpartition. It is not working for me. here is what I have `(output = session.read_until(b"%", timeout ))` `(output.rpartition(b'\r\n')[-1])` – mad5245 Apr 26 '13 at 20:05
  • 1
    What does "not working for me" mean? If you're just calculating `output.rpartition(b'\r\n')[-1]` and not storing or printing or otherwise using the result, it's not going to be very useful… – abarnert Apr 26 '13 at 20:07
  • Sorry I was editing like a madman trying to figure out how to get code in comments. It is not taking just the last line. It is still taking everything up to it. What does the -1 do? – mad5245 Apr 26 '13 at 20:10
  • 1
    For any list (or other sequence) `foo`, `foo[-1]` is the last element. The `rpartition(b'\r\n')` function returns everything up to the last `b'\r\n'`, then the `b'\r\n'` itself, then everything after it. So, putting them together, this gets everything after it. Are you sure you actually have `b'\r\n'` rather than `b'\n'` or some other line ending in your string? (Have you tried logging its `repr` to make sure?) – abarnert Apr 26 '13 at 20:12
  • 1
    Meanwhile, what does "It is not taking just the last line" mean? What is the "it" here? Again, you have to _use the result of the expression_. It's not going to change `output`, or any other variable, in-place, if that's what you're expecting. – abarnert Apr 26 '13 at 20:13
  • At the end I `print(output)`, I have these 4 lines at the end of the code `output = session.read_until(b"%", timeout )` `output.rpartition(b"CPU")` `session.close()` `print(output)` this returns everything up to the %. It does not trim the beginning to "CPU" – mad5245 Apr 26 '13 at 20:19
  • Thanks for the quick responses by the way. It is really helping me out – mad5245 Apr 26 '13 at 20:22
  • This may be stupid, but do I need an import for the rpartition or for line.split? – mad5245 Apr 26 '13 at 20:30
  • 1
    Once again, just calling `partition` on `output` does not change `output`, or any other variable in-place. It returns a new value. You have to store that new value in another variable, or print it out, or whatever, if you want to use it in any way. I'm not sure how I can explain this any more simply, but I'll try editing some sample code into to answer to see if it helps. – abarnert Apr 26 '13 at 20:46
  • Also, you never need to import anything to call a method on an object you already have, like `output` or `line`. – abarnert Apr 26 '13 at 20:46
  • So would something like `line = output.rpartition(b"CPU")` be what I am looking for. Then use `line` as the new variable? – mad5245 Apr 26 '13 at 20:51
  • @mad5245: Well, that will work as long as there's no other line with, say, "CPU Version : " in it, and it will give you the line you're looking for with the first 3 characters chopped off… but why? How is that better than getting the whole last line, with no caveats? – abarnert Apr 26 '13 at 21:06
  • You made that so much less of a headache than it was becoming. Thank you for the help!! – mad5245 Apr 26 '13 at 21:16
1

If you want to get only numbers:

>>> output = "CPU Utilization          : 5 %"
>>> [int(s) for s in output.split() if s.isdigit()]
[5]

>>> output = "CPU Utilization          : 5 % % 4.44 : 1 : 2"
>>> [int(s) for s in output.split() if s.isdigit()]
[5, 4.44, 1, 2]

EDIT:

for line in output:
    print line # this will print every single line in a loop, so you can make:
    print [int(s) for s in line.split() if s.isdigit()]
Johnny
  • 512
  • 2
  • 7
  • 18
  • I think this is what I need, but how would I get the output to just be that one line? There are settings before and after the "CPU Utilization : 5 %". Thanks – mad5245 Apr 26 '13 at 19:46
  • just loop through output (see my EDIT) – Johnny Apr 26 '13 at 22:32
  • Looping through the output doesn't help. He wants to get only the CPU Utilization number, not every number. – abarnert Apr 26 '13 at 22:38
1

As I understand the problem, you want to select 1 line out of a block of lines, but not necessarily the last line.

The line you're interested in always starts with "CPU Utilization"

This should work:

for line in output.splitlines():
  if 'CPU Utilization' in line:
    cpu_utilization = line.split()[-2]
jwygralak67
  • 924
  • 7
  • 13
  • According to the question, his `read_until` always ends with the "CPU Utilization" line, so there's no need to do this. – abarnert Apr 26 '13 at 20:08
  • Also, why would you explicitly call `line.__contains__(foo)` instead of just using `foo in line`? – abarnert Apr 26 '13 at 20:08
  • @abarnert Actually the OP stated, "I actually want a line in the middle of the output. And it is not in a static position " – jwygralak67 Apr 26 '13 at 20:20
  • So this method gives me the same as abarnert's method. It gives me all data up to and including the line I need. Is there an import I need that I am missing? – mad5245 Apr 26 '13 at 20:32
  • This method will step through the lines, looking for the one line containing the string "CPU Utilization". Once that line is found, it splits that line into chunks using whitespace as the delimiter. Then it takes the second from last chunk, which is the numeric part you're interested in, and stores it in the variable cpu_utilization. – jwygralak67 Apr 27 '13 at 15:51
0
In [27]: mystring= "% 5 %;%,;;;;;%"
In [28]: ''.join(c for c in mystring if c.isdigit())
Out[28]: '5'

faster way :

def find_digit(mystring):
    return filter(str.isdigit, mystring)

find_digit(mystring)
5
Moj
  • 6,137
  • 2
  • 24
  • 36
  • This seems like a really bad idea. If you give it `"CPU Utilization : 5 % % 4.44 : 1 : 2"`, you'll get the number 544412. And there's no reason to break it down character by character and then join back into a `word`; given that his number is clearly whitespace delimited, you can just go word by word in the first place. – abarnert Apr 26 '13 at 20:10
  • well I assumed it's the only number as it is written in the question: `but there are many ':' and '%' characters in the rest of the output` – Moj Apr 26 '13 at 20:14
  • Read his comments on the other answers. For example, "There are other settings before and after…", and presumably those settings lines have numbers as well as colons and percents. – abarnert Apr 26 '13 at 20:18
  • he posted this stuff after I submit my code. he probably should put this informations in the original question – Moj Apr 26 '13 at 20:22
  • Agreed, he should. But an answer which not useful for the OP's (or anyone's) real problem doesn't become useful just because it's technically correct owing to the OP not stating his question properly. – abarnert Apr 26 '13 at 20:55