0

I wrote a script that sends IOS files to my cisco device if there is enough free space, if there isn't enough free space- delete a file until there is. this works all fine and dandy unless the IOS happens to be in a directory.

pseudo code:

send directory command
parse output and put it in format
flash:c3560-ipservicesk9-mz.150-2.SE7.bin
flash:/testfolder/c3560-ipservicesk9-mz.120-2.SE7.bin

Actual code:

set index 0
send "dir /recursive \n"
expect {
        #this is the new code
        -nocase -re "directory of (\[^\r\]+)" {
                set test $expect_out(1,string)
                exp_continue
        }
        #old code that just grabbed all ios, working
        -nocase -re "(\[^\[:space:\]\]+.bin)" {
                append test ":$expect_out(1,string)"
                set ioses($index) $test
                set index [expr $index + 1]
                exp_continue
        }
        #final case which escapes the exp_continue, and sets the free space
        -nocase -re "\\((.*) bytes free" {set free $expect_out(1,string)}
}

here is an example of the output of "send dir /recursive"

Directory of flash:/*

2  -rwx    17620224  Apr 20 2015 00:49:13 +00:00  c3560-ipservicesk9-mz.150-2.SE7.bin
3  -rwx        2236   Mar 1 1993 00:01:02 +00:00  vlan.dat
4  -rwx        4560  Apr 22 2015 14:30:05 +00:00  private-config.text
7  -rwx       32329  Apr 17 2015 23:09:06 +00:00  backup_config
6  -rwx        3096  Apr 22 2015 14:30:05 +00:00  multiple-fs
8  -rwx       32344  Apr 22 2015 14:30:04 +00:00  config.text

Directory of flash:/testfolder

    9  -rwx        2236  Apr 23 2015 02:01:08 +00:00  c3560-ipservicesk9-mz.120-2.SE7.bin

27998208 bytes total (10151936 bytes free)

when I print out my array, I only have one value flash:/testfolder/c3560-ipservicesk9-mz.120-2.SE7.bin"

my algorithm is obviously wrong, how would go about parsing this data?

EDIT-

this is the code that I ended up with, although DINESH's code works as well

expect {
        -nocase -re "directory of.+#" {
                set input $expect_out(buffer)
                set filesystem $input
                set data [split $filesystem "\r"]
                foreach dat $data {
                        if { [regexp -nocase {directory of} $dat] } {
                                regexp -nocase {directory of (.+)} $dat -> fs
                                regsub -all {/\*} $fs "" fs
                        }
                        if { [regexp -nocase {bin} $dat] } {
                                regexp -nocase { ([^[:space:]]+bin)} $dat -> f
                                if { [regexp {/} $fs] } {
                                        lappend ioses $fs/$f
                                } else {
                                        lappend ioses $fs$f
                                }
                        }
                        if { [regexp -nocase {bytes free} $dat] } {
                                regexp -nocase {\((.*) bytes free} $dat -> free
                        }
                }
        }
}
genx1mx6
  • 425
  • 1
  • 6
  • 12
  • Your script will first set `test` variable to `flash:/*` and then it will be appended with `c3560-ipservicesk9-mz.150-2.SE7.bin`. At last the variable `free` will have the value as `10151936`. Now, do you need to match to the `testfolder` also ? – Dinesh Apr 23 '15 at 05:01
  • I probably should have mentioned it in the original post, but the value of my array turns out to only be "flash:/testfolder/c3560-ipservicesk9-mz.120-2.SE7.bin" and an empty index I would like to get a list of all the .bin files in my directories so I can delete until there is enough room for the new one – genx1mx6 Apr 23 '15 at 05:14

1 Answers1

1

Since you have to match multiple items, it is better to match all the content till you get the whole output of the command dir /recursive.

send "dir /recursive\r"
expect -re "(.*)<till your prompt>"

Here your prompt can be $ or > or #. One generalized approach can be like

set prompt "#|>|\\\$"; # Backslashes to match literal dollar sign

Once you have the whole content from expect_out array, apply regexp and get the results.

I am just assuming input variable having the whole content. To demonstrate the same, I have assigned it to one variable.

set input "
Directory of flash:/*

2  -rwx    17620224  Apr 20 2015 00:49:13 +00:00  c3560-ipservicesk9-mz.150-2.SE7.bin
3  -rwx        2236   Mar 1 1993 00:01:02 +00:00  vlan.dat
4  -rwx        4560  Apr 22 2015 14:30:05 +00:00  private-config.text
7  -rwx       32329  Apr 17 2015 23:09:06 +00:00  backup_config
6  -rwx        3096  Apr 22 2015 14:30:05 +00:00  multiple-fs
8  -rwx       32344  Apr 22 2015 14:30:04 +00:00  config.text

Directory of flash:/testfolder

    9  -rwx        2236  Apr 23 2015 02:01:08 +00:00  c3560-ipservicesk9-mz.120-2.SE7.bin

27998208 bytes total (10151936 bytes free)

"

# -all => To match all occurrence of the pattern in the given input
# -line => To enable newline-sensitive matching. By default, newline is a 
# completely ordinary character with no special meaning.
# -inline => To get the matched words as a list
set  dirnames [regexp -line -all -nocase -inline {directory of\s(\S+).*} $input]
#puts $dirnames
set filenames [regexp -line -all -inline {\S+\.bin} $input]
#puts $filenames
regexp {(\d+) bytes free} $input ignore freespace

foreach {ignore actualdir} $dirnames filename $filenames {
    //Checking if it contains '/*' or not.
    if {[regsub {/\*} $actualdir {} actualdir]} {
        lappend result $actualdir:$filename
    } else {
        lappend result "$actualdir/$filename"
    }
}   
foreach elem $result {
    puts $elem
}
puts "Free space : $freespace"

Output :

flash:c3560-ipservicesk9-mz.150-2.SE7.bin
flash:/testfolder/c3560-ipservicesk9-mz.120-2.SE7.bin
Free space : 10151936
Dinesh
  • 16,014
  • 23
  • 80
  • 122
  • That's nearly what I went with. I come from a more perl background and slowly learning the ins and outs of expect /tcl. – genx1mx6 Apr 23 '15 at 22:34
  • can you explain your regex flags? they all seem to do the same thing. also I'm assuming they return a list / array? I can't find any information on the "ignore" keyword you used, how does it work? I edited my original post with the code that I made to make it work, could you look at the it and compare to what you did? I think obviously mine is doing more work. `capture split on \r iterate through the array until we get intended values ` – genx1mx6 Apr 23 '15 at 23:23
  • sorry for the third comment, but I just realized your code assumes "flash" when in fact it's variable (could be bootflash:, disk0:, etc) `lappend result "flash:$filename"` – genx1mx6 Apr 24 '15 at 04:16
  • 1
    I have updated my answer. With respect to your query on `ignore`. It is not a keyword. It is just a variable same as what you have used like `->` in your code. (I wonder why you have some symbol to hold a variable). In `regexp`, if you have submatches, then it will be saved in the successive arguments. The first variable defined (such as like `ignore` or `->`) next to the string to match will hold the whole output. Have a look at [man](https://www.tcl.tk/man/tcl8.6/TclCmd/regexp.htm) page. – Dinesh Apr 24 '15 at 04:42
  • thanks for the update!! I understand your first IGNORE now, it contains the entire match..... What is the second IGNORE doing ? and why is it in braces? If I understand it right, you're iterating over two lists at the same time correct? like a said, pretty noob to expect / tcl also, I used "->" because it was the syntax I found on google that worked, but now I know the real meaning. – genx1mx6 Apr 24 '15 at 06:40
  • 1
    @genx1mx6 : Yes, you are correct. Due to the submatches, the `dirnames` will have 4 elements in the list. The order of the list is ` `. Since we have 2 matches, it contains 4 in which we need only the even elements. – Dinesh Apr 24 '15 at 07:15
  • thanks for the help, i'm going to review that loop a bit more to make sure I'm understanding what you did. the `{ignore actualdir}` part is taking two elements in the array at the same time right? (hence only the 'even' elements) the ` if {[regsub {/\*} $actualdir {} actualdir]} ` part is searching $actualdir for '/*', and storing the entire match into '{}' (null list? unnamed list?) and the actual match into actual dir. – genx1mx6 Apr 25 '15 at 09:06
  • 1
    The `regsub` command matches the regular expression pattern against string, replaces those matched string with given substitution string and either copies resultant string to the variable whose name is given by or returns string if is not present. `{\/*}` - pattern, `$actualdir` - string we want to check for that pattern, `{}` - substitution string, `actualdir` - save the the result into `actualdir`again. `{}` is actually a representation of empty string here. (`""` can also be used). Have a look at [man](https://www.tcl.tk/man/tcl/TclCmd/regsub.htm) page. – Dinesh Apr 25 '15 at 11:46
  • 1
    It is going bit lengthy. If still have a doubt, ask a separate question. :) – Dinesh Apr 25 '15 at 11:49
  • 1
    The `foreach` command implements a loop where the loop variable(s) take on values from one or more lists. {ignore actualdir} - 2 loop variables, `$dirname` - 1 list variable. On each iteration, 2 values from list is taken and processed. Have a look at [man](https://www.tcl.tk/man/tcl/TclCmd/foreach.htm) page for reference. – Dinesh Apr 25 '15 at 11:51