lispcommon-lispsbclcclcffi

How do I get the list of all environment variables available in a Lisp process?


I want to enumerate the list of all environment variables available in my Lisp process. I want the same list that is returned by the C variable environ.

Neither SBCL nor Clozure CL seem to provide it out of the box. Can I do it using CFFI?


Solution

  • Interfacing with the OS and common lisp is still quite dependent on your lisp distribution. So more or less is not easy to find a good library that works in all CL distributions.

    I recommend you to work with UIOP, which comes with ASDF 3 and above and it is the best library for working with the OS inside common lisp nowadays. However it doesn't have a function to list the environment variables, furthermother if we take a look at how it works with environment varables we can see that for every lisp distribution we have a different version of accessing the variables only for example:

    (defun getenv (x)
        "Query the environment, as in C getenv.
    Beware: may return empty string if a variable is present but empty;
    use getenvp to return NIL in such a case."
        (declare (ignorable x))
        #+(or abcl clasp clisp ecl xcl) (ext:getenv x)
        #+allegro (sys:getenv x)
        #+clozure (ccl:getenv x)
        #+cmucl (unix:unix-getenv x)
        #+scl (cdr (assoc x ext:*environment-list* :test #'string=))
        #+cormanlisp
        (let* ((buffer (ct:malloc 1))
               (cname (ct:lisp-string-to-c-string x))
               (needed-size (win:getenvironmentvariable cname buffer 0))
               (buffer1 (ct:malloc (1+ needed-size))))
          (prog1 (if (zerop (win:getenvironmentvariable cname buffer1 needed-size))
                     nil
                     (ct:c-string-to-lisp-string buffer1))
            (ct:free buffer)
            (ct:free buffer1)))
        #+gcl (system:getenv x)
        #+genera nil
        #+lispworks (lispworks:environment-variable x)
        #+mcl (ccl:with-cstrs ((name x))
                (let ((value (_getenv name)))
                  (unless (ccl:%null-ptr-p value)
                    (ccl:%get-cstring value))))
        #+mkcl (#.(or (find-symbol* 'getenv :si nil) (find-symbol* 'getenv :mk-ext nil)) x)
        #+sbcl (sb-ext:posix-getenv x)
        #-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl genera lispworks mcl mkcl sbcl scl xcl)
        (not-implemented-error 'getenv))
    
      (defsetf getenv (x) (val)
        "Set an environment variable."
          (declare (ignorable x val))
        #+allegro `(setf (sys:getenv ,x) ,val)
        #+clisp `(system::setenv ,x ,val)
        #+clozure `(ccl:setenv ,x ,val)
        #+cmucl `(unix:unix-setenv ,x ,val 1)
        #+ecl `(ext:setenv ,x ,val)
        #+lispworks `(hcl:setenv ,x ,val)
        #+mkcl `(mkcl:setenv ,x ,val)
        #+sbcl `(progn (require :sb-posix) (symbol-call :sb-posix :setenv ,x ,val 1))
        #-(or allegro clisp clozure cmucl ecl lispworks mkcl sbcl)
    '(not-implemented-error '(setf getenv))
    

    so for getting one variable:

    CL-USER> (uiop:getenv "GEM_PATH")
    "/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev:/Users/toni/.rvm/gems/ruby-2.1.6@global"
    

    as pointed in a comment if you are usign SBCL you can get this with it:

    SB-EXT:POSIX-ENVIRON

    example in c:

    extern char **environ;
    //...
    
    int i = 0;
    while(environ[i]) {
      printf("%s\n", environ[i++]); // prints in form of "variable=value"
    }
    
    | GEM_HOME=/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev                                                                                                                                                                                       |
    | SHELL=/bin/bash                                                                                                                                                                                                                         |
    | TERM=dumb                                                                                                                                                                                                                               |
    | TMPDIR=/var/folders/zs/t9wnzpqj2bdgjjgjwb8pqxc80000gn/T/                                                                                                                                                                                |
    | Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.e0K30SfWTc/Render                                                                                                                                                             |
    | USER=toni                                                                                                                                                                                                                               |
    | SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.KvlUaU8PD5/Listeners                                                                                                                                                                       |
    | __CF_USER_TEXT_ENCODING=0x1F5:0x0:0x8                                                                                                                                                                                                   |
    | PATH=/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev/bin:/Users/toni/.rvm/gems/ruby-2.1.6@global/bin:/Users/toni/.rvm/rubies/ruby-2.1.6/bin/:/usr/texbin:/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/toni/.rvm/bin |
    | PWD=/Users/toni/learn/lisp/cl-l/stackoverflow/scripts                                                                                                                                                                                   |
    | LANG=es_ES.UTF-8                                                                                                                                                                                                                        |
    | XPC_FLAGS=0x0                                                                                                                                                                                                                           |
    | XPC_SERVICE_NAME=org.gnu.Emacs.13016                                                                                                                                                                                                    |
    | SHLVL=1                                                                                                                                                                                                                                 |
    | HOME=/Users/toni                                                                                                                                                                                                                        |
    | BUNDLE_PATH=/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev                                                                                                                                                                                    |
    | LOGNAME=toni                                                                                                                                                                                                                            |
    | GEM_PATH=/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev:/Users/toni/.rvm/gems/ruby-2.1.6@global                                                                                                                                               |
    | DISPLAY=/private/tmp/com.apple.launchd.ljkiChb2wE/org.macosforge.xquartz:0                                                                                                                                                              |
    | _=/tmp/babel-6131RLH/C-bin-6131T_s                                                                                                                                                                                                      |
    

    example in SBCL

    (sb-ext:posix-environ)
    
    
    ("TERM=dumb" "TERMCAP=" "COLUMNS=139" "INSIDE_EMACS=25.2.1,comint"
     "BUNDLE_PATH=/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev"
     "GEM_PATH=/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev:/Users/toni/.rvm/gems/ruby-2.1.6@global"
     "GEM_HOME=/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev" "LANG=es_ES.UTF-8"
     "XPC_FLAGS=0x0" "USER=toni" "XPC_SERVICE_NAME=org.gnu.Emacs.13016"
     "DISPLAY=/private/tmp/com.apple.launchd.ljkiChb2wE/org.macosforge.xquartz:0"
     "LOGNAME=toni"
     "PATH=/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev/bin:/Users/toni/.rvm/gems/ruby-2.1.6@global/bin:/Users/toni/.rvm/rubies/ruby-2.1.6/bin/:/usr/texbin:/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/toni/.rvm/bin"
     "SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.KvlUaU8PD5/Listeners"
     "Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.e0K30SfWTc/Render"
     "SHELL=/bin/bash" "HOME=/Users/toni" "__CF_USER_TEXT_ENCODING=0x1F5:0x0:0x8"
     "TMPDIR=/var/folders/zs/t9wnzpqj2bdgjjgjwb8pqxc80000gn/T/"
     "SBCL_HOME=/Users/toni/.roswell/impls/x86-64/darwin/sbcl/1.3.16/lib/sbcl"
     "ROS_OPTS=((\"program\"\"(:eval\\\"(ros:quicklisp)\\\")\")(\"lispdir\"\"/usr/local/Cellar/roswell/17.4.8.76/etc/roswell/\")(\"verbose\"\"0\")(\"homedir\"\"/Users/toni/.roswell/\")(\"wargv0\"\"/usr/local/bin/ros\")(\"argv0\"\"/usr/local/bin/ros\")(\"quicklisp\"\"/Users/toni/.roswell/lisp/quicklisp/\")(\"impl\"\"sbcl/1.3.16\")(\"dynamic-space-size\"\"3gb\")(\"sbcl.version\"\"1.3.16\")(\"allegro.version\"\"100express\")(\"ccl-bin.version\"\"1.11\")(\"ecl.version\"\"16.1.3\")(\"slime.version\"\"2017.02.27\")(\"sbcl-bin.version\"\"1.2.11\")(\"default.lisp\"\"sbcl\"))")
    

    So for other implementations you mut investigate how to,

    Finally exist a good library called OSICAT that:

    OSICAT is a lightweight operating system interface for Common Lisp on Unix-platforms. It is not a POSIX-style API, but rather a simple lispy accompaniment to the standard ANSI facilities.

    so in your case you must use:

    (osicat:environment)
    
    (("TERM" . "dumb") ("TERMCAP" . "") ("COLUMNS" . "139") ("INSIDE_EMACS" . "25.2.1,comint") ("BUNDLE_PATH" . "/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev") ("GEM_PATH" . "/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev:/Users/toni/.rvm/gems/ruby-2.1.6@global") ("GEM_HOME" . "/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev") ("LANG" . "es_ES.UTF-8") ("XPC_FLAGS" . "0x0") ("USER" . "toni") ("XPC_SERVICE_NAME" . "org.gnu.Emacs.13016") ("DISPLAY" . "/private/tmp/com.apple.launchd.ljkiChb2wE/org.macosforge.xquartz:0") ("LOGNAME" . "toni") ("PATH" . "/Users/toni/.rvm/gems/ruby-2.1.6@afs-dev/bin:/Users/toni/.rvm/gems/ruby-2.1.6@global/bin:/Users/toni/.rvm/rubies/ruby-2.1.6/bin/:/usr/texbin:/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/toni/.rvm/bin") ("SSH_AUTH_SOCK" . "/private/tmp/com.apple.launchd.KvlUaU8PD5/Listeners") ("Apple_PubSub_Socket_Render" . "/private/tmp/com.apple.launchd.e0K30SfWTc/Render") ("SHELL" . "/bin/bash") ("HOME" . "/Users/toni") ("__CF_USER_TEXT_ENCODING" . "0x1F5:0x0:0x8") ("TMPDIR" . "/var/folders/zs/t9wnzpqj2bdgjjgjwb8pqxc80000gn/T/") ("ROS_OPTS" . "((\"program\"\"(:eval\\\"(ros:quicklisp)\\\")\")(\"lispdir\"\"/usr/local/Cellar/roswell/17.4.8.76/etc/roswell/\")(\"verbose\"\"0\")(\"homedir\"\"/Users/toni/.roswell/\")(\"wargv0\"\"/usr/local/bin/ros\")(\"argv0\"\"/usr/local/bin/ros\")(\"quicklisp\"\"/Users/toni/.roswell/lisp/quicklisp/\")(\"impl\"\"ccl-bin/1.11\")(\"dynamic-space-size\"\"3gb\")(\"sbcl.version\"\"1.3.16\")(\"allegro.version\"\"100express\")(\"ccl-bin.version\"\"1.11\")(\"ecl.version\"\"16.1.3\")(\"slime.version\"\"2017.02.27\")(\"sbcl-bin.version\"\"1.2.11\")(\"default.lisp\"\"ccl-bin\"))"))
    

    SO I hope that this should work