英文:
In haskell, how can I interact with stdin of an `IO ()`?
问题
这显示了我想做的事情的精神,它几乎可以工作(只是不适用于我的当前nix设置):
```haskell
echo :: IO ()
echo = getLine >>= putStrLn
testEcho :: IO ()
testEcho = do
(Just stdin_hdl, Just stdout_hdl, m_stderr_hdl, p_hdl)
<- createProcess $
(shell "nix run .#echo")
{ std_in = CreatePipe
, std_out = CreatePipe
}
hPutStrLn stdin_hdl "Hello, echo!"
hClose stdin_hdl
!response <- hGetContents stdout_hdl
cleanupProcess (Just stdin_hdl, Just stdout_hdl, m_stderr_hdl, p_hdl)
if response == "Hello, echo!" then
putStrLn "Success!"
else error "Failure"
基本上我想要类似于createProcess
的东西,但其类型为createProcess :: IO () -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
,而不是实际上的类型(createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
)。
对于这段代码的精神部分,你需要将echo
作为你的nix flake的一个应用,以便nix run .#echo
能够正常工作。
关于testEcho
不满意的地方在于需要使用一个shell命令(nix run .#echo
),而echo
可以作为Haskell代码在那里使用。testEcho
在我的nix环境中目前也出现了故障,但在从cabal repl
调用时运行良好。
<details>
<summary>英文:</summary>
This shows the spirit of what I want to do and it almost works (just not within within my current nix setup):
```haskell
echo :: IO ()
echo = getLine >>= putStrLn
testEcho :: IO ()
testEcho = do
(Just stdin_hdl, Just stdout_hdl, m_stderr_hdl, p_hdl)
<- createProcess $
(shell ("nix run .#echo"))
{ std_in = CreatePipe
, std_out = CreatePipe
}
hPutStrLn stdin_hdl "Hello, echo!"
hClose stdin_hdl
!response <- hGetContents stdout_hdl
cleanupProcess (Just stdin_hdl, Just stdout_hdl, m_stderr_hdl, p_hdl)
if response == "Hello, echo!" then
putStrLn "Success!"
else error "Failure"
basically I want something like createProcess
but with a type of createProcess :: IO () -> -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
instead of what it actually is (createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
)
For the spirit code, you would have to make echo
an app of your nix flake for nix run .#echo
to work
What is unsatisfactory about testEcho
is needing to use a shell command (nix run .#echo
) when echo
is available there as haskell code. testEcho
also currently malfunctions within my nix environment, but works well when called from cabal repl
.
答案1
得分: 5
你可以降级到unix
包,它提供了forkProcess
。它还提供了为通信设置一些新的文件描述符所需的内容。下面是一个完整的示例:
import System.IO
import System.Posix.Process
import System.Posix.IO
import System.Posix.Types
echo :: IO ()
echo = getLine >>= putStrLn
forkWithStandardFds :: IO () -> IO (ProcessID, Handle, Handle, Handle)
forkWithStandardFds act = do
(r0, w0) <- createPipe
(r1, w1) <- createPipe
(r2, w2) <- createPipe
pid <- forkProcess $ do
-- 六个closeFd不是严格必要的,
-- 但它们是良好的卫生习惯
closeFd w0 >> dupTo r0 stdInput
closeFd r1 >> dupTo w1 stdOutput
closeFd r2 >> dupTo w2 stdError
act
hIn <- closeFd r0 >> fdToHandle w0
hOut <- closeFd w1 >> fdToHandle r1
hErr <- closeFd w2 >> fdToHandle r2
pure (pid, hIn, hOut, hErr)
main :: IO ()
main = do
(pid, hIn, hOut, hErr) <- forkWithStandardFds echo
hPutStrLn hIn "Hello, echo!"
hFlush hIn
response <- hGetLine hOut
if response == "Hello, echo!" then putStrLn "Success!" else error "Failure"
getProcessStatus True False pid >>= print
运行时,它会打印:
Success!
Just (Exited ExitSuccess)
英文:
You can drop down a level to the unix
package, which offers forkProcess
. It also offers the stuff you need to set up a few fresh fds for comms. Here's a complete worked example:
import System.IO
import System.Posix.Process
import System.Posix.IO
import System.Posix.Types
echo :: IO ()
echo = getLine >>= putStrLn
forkWithStandardFds :: IO () -> IO (ProcessID, Handle, Handle, Handle)
forkWithStandardFds act = do
(r0, w0) <- createPipe
(r1, w1) <- createPipe
(r2, w2) <- createPipe
pid <- forkProcess $ do
-- the six closeFd's aren't strictly speaking necessary,
-- but they're good hygiene
closeFd w0 >> dupTo r0 stdInput
closeFd r1 >> dupTo w1 stdOutput
closeFd r2 >> dupTo w2 stdError
act
hIn <- closeFd r0 >> fdToHandle w0
hOut <- closeFd w1 >> fdToHandle r1
hErr <- closeFd w2 >> fdToHandle r2
pure (pid, hIn, hOut, hErr)
main :: IO ()
main = do
(pid, hIn, hOut, hErr) <- forkWithStandardFds echo
hPutStrLn hIn "Hello, echo!"
hFlush hIn
response <- hGetLine hOut
if response == "Hello, echo!" then putStrLn "Success!" else error "Failure"
getProcessStatus True False pid >>= print
When run, it prints
Success!
Just (Exited ExitSuccess)
答案2
得分: 2
你可以在不创建另一个进程的情况下完成这个操作,通过重定向 stdin
和 stdout
。这个方法效果很好:
import Control.Exception (bracket)
import GHC.IO.Handle (hDuplicate, hDuplicateTo)
import System.Directory (getTemporaryDirectory, removeFile)
import Test.Hspec
echo :: IO ()
echo = getLine >>= putStrLn
main :: IO ()
main = hspec $ do
specify "echo" $ do
let line = "Hello, echo!\n"
captureStdout (provideStdin line echo)
`shouldReturn`
line
provideStdin :: String -> IO a -> IO a
provideStdin input action =
withTemporaryFile $ \h -> do
hPutStr h input
hSeek h AbsoluteSeek 0
redirectStdin h action
captureStdout :: IO a -> IO String
captureStdout action =
withTemporaryFile $ \h -> do
redirectStdout h $ action *> hFlush stdout
hSeek h AbsoluteSeek 0
hGetContents' h
redirectStdin :: Handle -> IO a -> IO a
redirectStdin h action =
bracket
(hDuplicate stdin <* hDuplicateTo h stdin)
(\saved -> hDuplicateTo saved stdin *> hClose saved)
(const action)
redirectStdout :: Handle -> IO a -> IO a
redirectStdout h action =
bracket
(hFlush stdout *> hDuplicate stdout <* hDuplicateTo h stdout)
(\saved -> hDuplicateTo saved stdout *> hClose saved)
(const action)
withTemporaryFile :: (Handle -> IO a) -> IO a
withTemporaryFile inner = do
tmp <- getTemporaryDirectory
bracket
(openBinaryTempFile tmp "wtf-")
(\(name, h) -> hClose h >> removeFile name)
(inner . snd)
我相信你可以连续执行多个这样的操作,而不会影响原始的 stdin
和 stdout
,但我没有测试过。
英文:
You can do it without creating another process, by redirecting stdin
and stdout
. This works nicely:
import Control.Exception (bracket)
import GHC.IO.Handle (hDuplicate, hDuplicateTo)
import System.Directory (getTemporaryDirectory, removeFile)
import Test.Hspec
echo :: IO ()
echo = getLine >>= putStrLn
main :: IO ()
main = hspec $ do
specify "echo" $ do
let line = "Hello, echo!\n"
captureStdout (provideStdin line echo)
`shouldReturn`
line
provideStdin :: String -> IO a -> IO a
provideStdin input action =
withTemporaryFile $ \h -> do
hPutStr h input
hSeek h AbsoluteSeek 0
redirectStdin h action
captureStdout :: IO a -> IO String
captureStdout action =
withTemporaryFile $ \h -> do
redirectStdout h $ action *> hFlush stdout
hSeek h AbsoluteSeek 0
hGetContents' h
redirectStdin :: Handle -> IO a -> IO a
redirectStdin h action =
bracket
(hDuplicate stdin <* hDuplicateTo h stdin)
(\saved -> hDuplicateTo saved stdin *> hClose saved)
(const action)
redirectStdout :: Handle -> IO a -> IO a
redirectStdout h action =
bracket
(hFlush stdout *> hDuplicate stdout <* hDuplicateTo h stdout)
(\saved -> hDuplicateTo saved stdout *> hClose saved)
(const action)
withTemporaryFile :: (Handle -> IO a) -> IO a
withTemporaryFile inner = do
tmp <- getTemporaryDirectory
bracket
(openBinaryTempFile tmp "wtf-")
(\(name, h) -> hClose h >> removeFile name)
(inner . snd)
I believe you could do several of these in a row without messing up the original stdin
and stdout
, but I haven't tested that.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论