10

I can easily read an environment variable using System.Environment.getEnv. However, System.Environment does not have any corresponding setEnv function (as far as I could tell).

How do I set an environment variable from a Haskell program? I would prefer a cross-platform solution. (So just executing export VAR=val or using System.Posix.Env is not quite what I'm looking for.)

Tikhon Jelvis
  • 67,485
  • 18
  • 177
  • 214

3 Answers3

4

Note that base 4.7.0 now has a setEnv in System.Environment. So for the present or near future it's sorted out.

However if you need this feature in versions <4.7.0 (which is my case currently), I have also extracted from the commit that adds the feature the functions needed to make this work with an older base version.

However I seriously lost patience on this one and did it rather ugly but it works for me...

The mess is that there are 3 functions to call in a windows environment: putenv, SetEnvironmentVariableA (ASCII) and SetEnvironmentVariableW (widechar, utf16). The patch that was committed to base 4.7 does this automatically, but I did something more ugly by lack of time (I may yet it clean it up).

Here's what I have:

setEnv_ :: String -> String -> IO () 
setEnv_ key value = withCString key $ \k -> withCString value $ \v -> do 
  success <- c_SetEnvironmentVariable k v 
  unless success (throwGetLastError "setEnv") 

putEnv :: String -> IO ()
putEnv v = void (withCString v $ \vv -> c_putenv vv)

foreign import stdcall unsafe "windows.h SetEnvironmentVariableA" 
  c_SetEnvironmentVariable :: CString -> CString -> IO Bool 

-- SetEnv_ :: String -> String -> IO () 
-- SetEnv_ key value = withCWString key $ \k -> withCWString value $ \v -> do 
--   success <- c_SetEnvironmentVariable k v 
--   unless success (throwGetLastError "setEnv") 
--  
-- Foreign import stdcall unsafe "windows.h SetEnvironmentVariableW" 
--   c_SetEnvironmentVariable :: LPTSTR -> LPTSTR -> IO Bool 

foreign import ccall unsafe "putenv" c_putenv :: CString -> IO CInt 

Obviously use CPP to put the whole thing in a #ifdef for windows only. As you can see I have the code for the widechar call, but I commented it currently. I think for my use-case it would probably be enough to just call putenv but well it works as it is. So here's how I call it then:

setEnv_ "LANG" localeStr
putEnv $ "LANG=" ++ localeStr

My problem is that I'm primarily a linux user at home and I don't like doing too much work on windows at home, and I put a lot of energy to get this and other things to work properly on windows, and I can't bring myself to clean this up further. But with this code and the original patch you should get this to work on base <4.7 without much problems.

Emmanuel Touzery
  • 9,008
  • 3
  • 65
  • 81
3

On POSIX platforms you can use System.Posix.Env, which has a putEnv function. That's more portable than running export, although unfortunately not truly cross-platform.

Matthew Walton
  • 9,809
  • 3
  • 27
  • 36
  • 1
    Thanks for the answer. I actually saw that module after posting the question, but I would really like something that would work on Windows. – Tikhon Jelvis Feb 14 '12 at 10:26
2

A hint that this is not possible cross platform is that the Java API does not have a putenv. See also this related post.

The solution for the most frequent use case is to pass an appropriately constructed environment when exec-ing programs.

Community
  • 1
  • 1
Ingo
  • 36,037
  • 5
  • 53
  • 100