LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - General (http://www.linuxquestions.org/questions/linux-general-1/)
-   -   How do I extract data from nmblookup in bash shell script? (http://www.linuxquestions.org/questions/linux-general-1/how-do-i-extract-data-from-nmblookup-in-bash-shell-script-760089/)

utahnix 10-06-2009 12:44 PM

How do I extract data from nmblookup in bash shell script?
 
Okay...

I am working on a BASH shell script that does a scan of a subnet with nmap and returns the "live" IP's in the specified range. That part works great.

It's then supposed to query each "live" IP for it's NetBIOS hostname using nmblookup (I've got a Linux server playing PDC for a bunch of Windows workstations - all at a remote site). The problem is, I'm having trouble getting the data out of the nmblookup output.

Once I have the IP, I do something like this:
nmblookup -A <ip_address>

In my script, <ip_address> is naturally replaced with a variable. The problem is, I get output like this (from nmblookup):

Code:

        PCNAME          <00> -        M <ACTIVE>
        OFFICEDOMAIN    <00> - <GROUP> M <ACTIVE>
        PCNAME          <20> -        M <ACTIVE>
        OFFICEDOMAIN    <1e> - <GROUP> M <ACTIVE>

        MAC Address = xx-xx-xx-xx-xx-xx

What I need to do is extract 'PCNAME' and stuff it in a variable. Naturally 'PCNAME' is going to be different each time.

I would imagine awk would do the job, and maybe even sed... but I'm not super savvy with either. I've used perl in the past (esp for one liners), but my perl skills are awfully rusty. I've tried a number of things, and none of them worked.

I tried using grep and cut, but that didn't go well either. I thought perhaps I could use those two to get my variable out, and then use sed to remove the whitespace... but again, no go. I'm obviously doing something wrong.

So is there an easy and efficient way to get this data out?

lutusp 10-06-2009 01:17 PM

Quote:

Originally Posted by utahnix (Post 3709785)
Okay...

I am working on a BASH shell script that does a scan of a subnet with nmap and returns the "live" IP's in the specified range. That part works great.

It's then supposed to query each "live" IP for it's NetBIOS hostname using nmblookup (I've got a Linux server playing PDC for a bunch of Windows workstations - all at a remote site). The problem is, I'm having trouble getting the data out of the nmblookup output.

Once I have the IP, I do something like this:
nmblookup -A <ip_address>

In my script, <ip_address> is naturally replaced with a variable. The problem is, I get output like this (from nmblookup):

Code:

        PCNAME          <00> -        M <ACTIVE>
        OFFICEDOMAIN    <00> - <GROUP> M <ACTIVE>
        PCNAME          <20> -        M <ACTIVE>
        OFFICEDOMAIN    <1e> - <GROUP> M <ACTIVE>

        MAC Address = xx-xx-xx-xx-xx-xx

What I need to do is extract 'PCNAME' and stuff it in a variable. Naturally 'PCNAME' is going to be different each time.

I would imagine awk would do the job, and maybe even sed... but I'm not super savvy with either. I've used perl in the past (esp for one liners), but my perl skills are awfully rusty. I've tried a number of things, and none of them worked.

I tried using grep and cut, but that didn't go well either. I thought perhaps I could use those two to get my variable out, and then use sed to remove the whitespace... but again, no go. I'm obviously doing something wrong.

So is there an easy and efficient way to get this data out?

No, not really. The problem is that you cannot say in advance what the data will look like. Sometimes nmblookup won't be able to provide the list you expect, sometimes the list will look different than you expect.

The other problem is that the output is multi-line, which eliminates line-oriented solutions like sed.

All I can say is, catalog all the possible responses from nmblookup and deal with all of them. For this you need more coding skills. IOW you have bitten off more than you can comfortably chew, but you could certainly learn how to solve this problem.

David the H. 10-06-2009 02:02 PM

Quote:

Originally Posted by lutusp (Post 3709815)
The other problem is that the output is multi-line, which eliminates line-oriented solutions like sed.

That's not entirely true. Sed can operate over multiple lines. It's just a bit more tricky to do so. How difficult it is depends on just what you're trying to extract.

Of course, whether you use sed or awk or whatever, the real requirement for a job like this is that first you have to find some kind of unique regular pattern to match. If you can determine that the data field you need always appears 2 lines after a certain fixed string, for example, or always appears at the beginning of a line and has 8-10 characters (and no other line does), or something like that, then it should be possible to come up with some way, though not necessarily a simple one, to extract it.

You may need to use more than one pattern match to catch all the different variations you could encounter in the input. Just make sure that whatever pattern(s) you use can uniquely identify the data field you want without any false negatives or false positives.

Also, are there any options you can use in nmblookup to output the data in a more convenient format? Or are there any other tools you can use to provide the same information?

chrism01 10-07-2009 12:39 AM

Given it's a multi-line output, and the results will vary if eg not a nmb host or whatever, then yes, catalogue all the expected result set types and figure how to match them. Allow for 'unmatched' ie one you didn't know could happen.

I'd personally do it in Perl, probably using a piped open or IPC::Open2 or IPC::Open3 .
If I had to do it in shell, the easiest way would be to redirect the output into a temp file eg nmblookup.dat & then parse the file.
Trying to do it on the fly in bash is tricky.

PS good Perl examples/howto
http://perldoc.perl.org/
http://www.perlmonks.org/?node=Tutorials

lutusp 10-07-2009 02:57 AM

Quote:

Originally Posted by David the H. (Post 3709879)
That's not entirely true. Sed can operate over multiple lines.

Yes, and one can do Calculus with an abacus -- it's just very, very difficult. The OP is much better off using a solution in which multi-line matches are more intuitive, readable and understandable than is sed when processing multi-line tasks.

catkin 10-07-2009 03:35 AM

How about this
Code:

<whatever nmbscan command, with output redirected to nmbscan.out>
PCNAME_00=''
PCNAME_20=''
while read line
do
    array=( $line )
    case "${array[1]}" in
        '<00>' )
            PCNAME_00="'${array[0]}'"
            ;;
        '<20>' )
            PCNAME_20="'${array[0]}'"
            ;;
    esac
done < nmbscan.out
echo "PCNAME_00: '$PCNAME_00', PCNAME_20: '$PCNAME_20'"


zhjim 10-07-2009 04:13 AM

@catkin
If the output stays like the provided example your example would print out the second line with officegroup and group. It also has the <00> in it.

I'd go with some awk and would distinguish the line depending on the numbers of fields.
Line 1 with the PCNAME has 5 fields where OFFICEGROUP lines have 6 fields. As long this holds true you could do something like this

Code:

awk { if (NF == 5 ) print $1
and you should get the PCNAME. Maybe do a grep "<00>" beforehand so you only have to awk two lines.

Nother thing if you can be sure that the PCNAME always is the first line go with
Code:

head -n 1

catkin 10-07-2009 05:01 AM

Quote:

Originally Posted by zhjim (Post 3710684)
@catkin
If the output stays like the provided example your example would print out the second line with officegroup and group. It also has the <00> in it.

Ooops! Refined version (I have also taken out some single quotes that should not have been there)
Code:

<whatever nmbscan command, with output redirected to nmbscan.out>
PCNAME_00=''
PCNAME_20=''
while read line
do
    array=( $line )
    case "${array[1]}" in
        '<00>' )
            if [[ "${array[0]}" = 'M" ]]; then
                PCNAME_00="${array[0]}"
            fi
            ;;
        '<20>' )
            PCNAME_20="${array[0]}"
            ;;
    esac
done < nmbscan.out
echo "PCNAME_00: '$PCNAME_00', PCNAME_20: '$PCNAME_20'"

The basic framework can be refined with further if-else or case-esac commands, as appropriate, to deal with all the output line types from nmbscan.

zhjim 10-07-2009 08:43 AM

Beside of a little typo I don't understand why the improved version would print the PCNAME. M is also in both lines and not realy array[0] but array[3] inside the PCNAME line. :confused:

Code:

    case "${array[1]}" in
        '<00>' )
            if [[ "${array[0]}" = 'M" ]]; then
                PCNAME_00="${array[0]}"
            fi
            ;;
        '<20>' )
            PCNAME_20="${array[0]}"
            ;;
    esac

I guess you meant array[3] inside the '<00>' if statement. I would agree on that :D And the PCNAME would be printed.

The typo I mentioned is right before the M or is it after? from the escape char you used I'd say its before ;)

Code:

if [[ "${array[0]}" = 'M" ]]; then
Code:

if [[ "${array[0]}" = "M" ]]; then
None the less its great input you gave and a nice and elegant solution.

catkin 10-07-2009 08:54 AM

Quote:

Originally Posted by zhjim (Post 3710887)
I guess you meant array[3] inside the '<00>' if statement. I would agree on that :D And the PCNAME would be printed.

The typo I mentioned is right before the M or is it after?

Correct regards the array subscript and either double or single quotes would work but single are the saner choice. One of each definitely won't do! Sorry about the errors.

EDIT: actually the errors were *cough* *cough* a deliberate challenge :)

David the H. 10-07-2009 09:06 AM

Quote:

Originally Posted by lutusp (Post 3710633)
Yes, and one can do Calculus with an abacus -- it's just very, very difficult. The OP is much better off using a solution in which multi-line matches are more intuitive, readable and understandable than is sed when processing multi-line tasks.

And as I said, it depends on exactly what you're trying to match. Matching one line and then grabbing something from the line immediately following it isn't all that hard to do. But if the data string you want can itself span multiple lines, for example, then yes, there are often better tools you can use.

Sed may not always be the ideal choice for multi-line matches, but it's not really as bad as you're making it sound. It's often better than awk in such cases, at the very least.

zhjim 10-07-2009 09:10 AM

Quote:

Originally Posted by catkin (Post 3710896)
EDIT: actually the errors were *cough* *cough* a deliberate challenge :)

And we all love them challenges ;)

utahnix 10-07-2009 05:46 PM

Okay, hmm... lots of things to consider.

I can probably grep the first line out of the file. I know that's assuming a little, but I can live with that.

At one point, I was able to get the name out, but it had all this white space on the sides. How would I use sed to remove the white space from a single line?

Or, considering the perl factor. If I honed that data down to one line with grep, is there a one-liner (i.e. perl -pe "<CODE>") that would yank that variable name from the line? And if I could get it out, how do I stick the output from that perl one-liner and stuff it back into a BASH perl script?

Help thus far is appreciated... any thoughts on this post?

chrism01 10-08-2009 12:45 AM

If you only had one line of data, use awk,; when it picks a field eg $3, doesn't care how much whitespace exists in between each non-whitespace value eg

Code:

  asd fgh

is the same as

asd        fgh

to awk; $1 is still 'fgh'. That's its advantage over 'cut'.

zhjim 10-08-2009 04:32 AM

Quote:

Originally Posted by utahnix (Post 3711501)
I can probably grep the first line out of the file. I know that's assuming a little, but I can live with that.

If you only want the first line go with head -n 1. I bet to grep some unkown thing is too much even for grep xD.

Quote:

Originally Posted by utahnix (Post 3711501)
At one point, I was able to get the name out, but it had all this white space on the sides. How would I use sed to remove the white space from a single line?

Or, considering the perl factor.
Help thus far is appreciated... any thoughts on this post?

This also goes into the direction of David the H.
Always remember why there are so many tools (binarys) included with a linux distribution. They all do one thing very good. But this also leaves the challenge to the user which tool to use. Obvious the tool that fullfils the task (cause each and every tool just does it best ;))
So to make my point across and illustrate what I want to say lets take a look at the first line of the man pages of all the tools mentioned through out this thread.

Code:

grep, egrep, fgrep, rgrep - print lines matching a pattern
So if we don't have a pattern we can't realy use grep

Code:

sed - stream editor for filtering and transforming text
okay somehow we want to filter but we absolutly don't want to transform text. Makes it on the list

Code:

mawk - pattern scanning and text processing language
okay same aplies here like it does to grep put also one point for awk cause it process text. Makes it on the list

Code:

head - output the first part of files
If we dare we consider the first line to be hostname. So this is a total match.

Code:

cut - remove sections from each line of files
this would be good when we found our line. (Just summing up not taking experience into it)

I know this is overkill. I Just wanted to see you all how many tools there are and the more you know the better the chances that you'll find the right one for the job. It's just like moving when you have a loot of friends. It just flies.

To boil things down. Either take the first line with head -n 1 or do the pattern matching of 5 fields within the PCNAME line. awk or the bash script will both suite you well


All times are GMT -5. The time now is 01:02 AM.