4

I’ve done quite a lot of source level blocks in org-mode, but I think I have a failure scenario if called functions inside the source code blocks are recursive.

The ifconfig program displays all the important information, just in the wrong format. Let’s fix that. First, let’s retrieve the results and we’ll store that in a name, ifconfig_data:

#+NAME: ifconfig_data
#+BEGIN_SRC sh :results drawer
  ifconfig -a
#+END_SRC

Which spills out:

#+RESULTS: ifconfig_data
:RESULTS:
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
        options=3<RXCSUM,TXCSUM>
        inet6 ::1 prefixlen 128
        inet 127.0.0.1 netmask 0xff000000
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
        nd6 options=1<PERFORMNUD>
gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
stf0: flags=0<> mtu 1280
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        ether 6c:40:08:b7:ce:b8
        inet6 fe80::6e40:8ff:feb7:ceb8%en0 prefixlen 64 scopeid 0x4
        inet 192.168.77.40 netmask 0xfffffe00 broadcast 192.168.77.255
        nd6 options=1<PERFORMNUD>
        media: autoselect
        status: active
...

We can access that data easily in standard org-mode Babel thingie:

#+BEGIN_SRC elisp :var datastring=ifconfig_data :results value
  (format "Results: %s" datastring)
#+END_SRC

However, the output format is awful, and I would like to have a list of tuples, where each tuple has the interface and the IP address. Since some interfaces may not have an IPv4 address (which is what I’m wanting).

To address this, I have a recursive function that calls itself for each regular expression match:

1. Matched the Interface
   2. The name of the Interface
3. Matches the Address
   4. Optional `addr:` section that we don’t care about
   5. The value of the address

If the match is on an address, we assume that we passed in the interface name as a header parameter, so that we can /build up/ the result.

#+NAME: ifconfig-display
#+BEGIN_SRC elisp :var datastring=ifconfig_data :results value
  (defun split-ifconfig (datastr pos header)
    "Assumes that the DATASTR is a string datastrput from ifconfig.  The POS is position within the string to search.  The HEADER is the current interface address."
    (when (string-match "\\(^\\([^ :]+\\):?\\ \\)\\|\\( *inet \\(addr:\\)?\\([0-9.]+\\)\\)" datastr pos)
      (cond
       ;; Interface:: Recurse with the name of this new header
       ((match-string 1 datastr)
            (split-ifconfig datastr (match-end 0) (match-string 2 datastr)))

       ;; Address:: Return a list with our interface header, the
       ;;           address, and the results of parsing the rest
       ((match-string 3 datastr)
            (cons (list header (match-string 5 datastr))
                    (split-ifconfig datastr (match-end 0) nil))))))

  (split-ifconfig datastring 0 nil)
#+END_SRC

This works if I evaluate the function stuff directly with C-x C-e.

The last line, however, in the code block returns nil. Always. The datastring contains the data. The function, split-ifconfig that was defined in the function is called. Still no go.

This doesn’t work, even if I pre-load the function into the global function space, and call it from the block:

#+BEGIN_SRC elisp :var out=ifconfig_data :results value
  (split-ifconfig out 0 nil)
#+END_SRC

I’ve tried making the datastr aspect a global variable, and not keep passing it. Still no go.

2
  • 1
    Are you the author of Literate DevOps?
    – Melioratus
    Commented Jan 7, 2016 at 22:18
  • Sorry, but I just noticed this question. Yes, that is my blog. Commented Mar 28, 2016 at 22:58

1 Answer 1

3

I think that the problem was with the regular expression matching the preceding end-line character before the protocol name. Below is your version with that problem fixed, plus a non-recursive version, in case you suspect it has anything to do with recursion.

#+NAME: ifconfig_data
#+BEGIN_SRC sh :results drawer
  ifconfig -a
#+END_SRC

#+NAME: ifconfig-display
#+BEGIN_SRC elisp :var datastring=ifconfig_data :results value
  (defun split-ifconfig (datastr pos header)
    "Assumes that the DATASTR is a string datastrput from ifconfig.  
      The POS is position within the string to search.  
      The HEADER is the current interface address."
    (when (string-match "\\(^\\([^ :]+\\):?\\ \\)\\|\\( *inet \\(addr:\\)?\\([0-9.]+\\)\\)"
                        datastr pos)
      (cond
       ;; Interface:: Recurse with the name of this new header
       ((match-string 1 datastr)
        (split-ifconfig datastr (match-end 0) (match-string 2 datastr)))

       ;; Address:: Return a list with our interface header, the
       ;;           address, and the results of parsing the rest
       ((match-string 3 datastr)
        (cons (list (org-trim header) (match-string 5 datastr))
              (split-ifconfig datastr (match-end 0) nil))))))

  (split-ifconfig datastring 0 nil)
#+END_SRC

#+RESULTS: ifconfig-display
| lo   |     127.0.0.1 |
| ppp0 | xx.xxx.xxx.xx |

#+NAME: ifconfig-display-2
#+BEGIN_SRC elisp :var datastring=ifconfig_data :results value
  (defun split-ifconfig (datastr pos header)
    "Assumes that the DATASTR is a string datastrput from ifconfig.  
          The POS is position within the string to search.  
          The HEADER is the current interface address."
    (cl-loop with state = :after
             and interface = nil
             and address = nil
             and result = nil
             for line in (split-string datastr "\n" nil "[\t ]+") do
             (cond
              ((string-equal line "")
               (when address
                 (push `(,interface ,address) result))
               (setf state :after interface nil address nil))
              ((eql state :after)
               (setf interface (cl-first (split-string line "[:\t ]"))
                     state :inside))
              ((string-prefix-p "inet " line)
               (setf address (cl-second (split-string line)))))
             finally (cl-return (nreverse result))))

  (split-ifconfig datastring 0 nil)
#+END_SRC

#+RESULTS: ifconfig-display-2
| lo   |     127.0.0.1 |
| ppp0 | xx.xxx.xxx.xx |

PS, you could have tested the function by doing something like (in *scratch* or REPL:

(let ((datastring (shell-command-to-string "ifconfig -a")))
  (split-ifconfig datastring 0 nil))

You'd see right away where the problem is.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.