Peter.O
Peter.O

Reputation: 6856

In elisp, how to evaluate a string of "var=value\n..." into lisp variables of the same name?

An mplayer tool (midentify) outputs "shell-ready" lines intended to be evaluated by a bash/sh/whatever interpreter.

How can I assign these var-names to their corresponding values as elisp var-names in emacs? The data is in a string (via shell-command-to-string)

Here is the data

ID_AUDIO_ID=0
ID_FILENAME=/home/axiom/abc.wav
ID_DEMUXER=audio
ID_AUDIO_FORMAT=1
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=0
ID_AUDIO_NCH=1
ID_LENGTH=3207.00
ID_SEEKABLE=1
ID_CHAPTERS=0
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=32000
ID_AUDIO_NCH=1
ID_AUDIO_CODEC=pcm
ID_EXIT=EOF

Upvotes: 3

Views: 601

Answers (4)

kevin cline
kevin cline

Reputation: 2736

Here's a function you can run on the output buffer:

(defun set-variables-from-shell-assignments ()
  (goto-char (point-min))
  (while (< (point) (point-max))
    (and (looking-at "\\([A-Z_]+\\)=\\(.*\\)$")
       (set (intern (match-string 1)) (match-string 2)))
    (forward-line 1)))

Upvotes: 2

desudesudesu
desudesudesu

Reputation: 2335

I don't think regexp is what really need. You need to split your string by \n and =, so you just say exactly the same to interpreter.

I think you can also use intern to get symbol from string(and set variables). I use it for the first time, so comment here if i am wrong. Anyways, if list is what you want, just remove top-level mapcar.

(defun set=(str)
  (mapcar (lambda(arg)
            (set
              (intern (car arg))
              (cadr arg)))
    (mapcar (lambda(arg)
              (split-string arg "=" t))
      (split-string
        str
        "\n" t))))

(set=
"ID_AUDIO_ID=0
ID_FILENAME=/home/axiom/abc.wav
ID_DEMUXER=audio
ID_AUDIO_FORMAT=1
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=0
ID_AUDIO_NCH=1
ID_LENGTH=3207.00
ID_SEEKABLE=1
ID_CHAPTERS=0
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=32000
ID_AUDIO_NCH=1
ID_AUDIO_CODEC=pcm
ID_EXIT=EOF")

Upvotes: 1

Sean
Sean

Reputation: 29772

Here's a routine that takes a string containing midentify output, and returns an association list of the key-value pairs (which is safer than setting Emacs variables willy-nilly). It also has the advantage that it parses numeric values into actual numbers:

(require 'cl)  ; for "loop"
(defun midentify-output-to-alist (str)
  (setq str (replace-regexp-in-string "\n+" "\n" str))
  (setq str (replace-regexp-in-string "\n+\\'" "" str))
  (loop for index = 0 then (match-end 0)
        while (string-match "^\\(?:\\([A-Z_]+\\)=\\(?:\\([0-9]+\\(?:\\.[0-9]+\\)?\\)\\|\\(.*\\)\\)\\|\\(.*\\)\\)\n?" str index)
        if (match-string 4 str)
        do (error "Invalid line: %s" (match-string 4 str))
        collect (cons (match-string 1 str)
                      (if (match-string 2 str)
                          (string-to-number (match-string 2 str))
                        (match-string 3 str)))))

You'd use this function like so:

(setq alist (midentify-output-to-alist my-output))
(if (assoc "ID_LENGTH" alist)
    (setq id-length (cdr (assoc "ID_LENGTH" alist)))
  (error "Didn't find an ID_LENGTH!"))

EDIT: Modified function to handle blank lines and trailing newlines correctly.

The regexp is indeed a beast; Emacs regexps are not known for their easiness on the eyes. To break it down a bit:

  • The outermost pattern is ^(?:valid-line)|(.*). It tries to match a valid line, or else matches the entire line (the .*) in match-group 4. If (match-group 4 str) is not nil, that indicates that an invalid line was encountered, and an error is raised.
  • valid-line is (word)=(?:(number)|(.*)). If this matches, then the name part of the name-value pair is in match-string 1, and if the rest of the line matches a number, then the number is in match-string 2, otherwise the entire rest of the line is in match-string 3.

Upvotes: 5

Luke Girvin
Luke Girvin

Reputation: 13432

There's probably a better way but this should do it:

(require 'cl)

(let ((s "ID_AUDIO_ID=0
ID_FILENAME=/home/axiom/abc.wav
ID_DEMUXER=audio
ID_AUDIO_FORMAT=1
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=0
ID_AUDIO_NCH=1
ID_LENGTH=3207.00
ID_SEEKABLE=1
ID_CHAPTERS=0
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=32000
ID_AUDIO_NCH=1
ID_AUDIO_CODEC=pcm
ID_EXIT=EOF"))
  (loop for p in (split-string s "\n")
    do
    (let* ((elements (split-string p "="))
          (key (elt elements 0))
          (value (elt elements 1)))
      (set (intern key) value))))

Upvotes: 3

Related Questions