12

catHaskellで簡単なプログラムを書こうとしています。複数のファイル名を引数として取り、各ファイルを順番にSTDOUTに書き込みたいのですが、私のプログラムは1つのファイルしか出力せずに終了します。

渡された最初のファイルだけでなく、すべてのファイルをコードで印刷するにはどうすればよいですか?

import Control.Monad as Monad
import System.Exit
import System.IO as IO
import System.Environment as Env

main :: IO ()
main = do
    -- Get the command line arguments
    args <- Env.getArgs

    -- If we have arguments, read them as files and output them
    if (length args > 0) then catFileArray args

    -- Otherwise, output stdin to stdout
    else catHandle stdin

catFileArray :: [FilePath] -> IO ()
catFileArray files = do
    putStrLn $ "==> Number of files: " ++ (show $ length files)
    -- run `catFile` for each file passed in
    Monad.forM_ files catFile

catFile :: FilePath -> IO ()
catFile f = do
    putStrLn ("==> " ++ f)
    handle <- openFile f ReadMode
    catHandle handle

catHandle :: Handle -> IO ()
catHandle h = Monad.forever $ do
    eof <- IO.hIsEOF h
    if eof then do
        hClose h
        exitWith ExitSuccess
    else
        hGetLine h >>= putStrLn

私は次のようなコードを実行しています:

runghc cat.hs file1 file2
4

4 に答える 4

20

あなたの問題は、exitWithプログラム全体を終了させることです。したがって、foreverファイルの最後まで関数を「永久に」実行したくないので、ファイルをループするために実際に使用することはできません。catHandleこのように書き直すことができます

catHandle :: Handle -> IO ()
catHandle h = do
    eof <- IO.hIsEOF h
    if eof then do
        hClose h
     else
        hGetLine h >>= putStrLn
        catHandle h

つまり、EOFに達していない場合は、繰り返して別の行を読み取ります。

ただし、このアプローチ全体は非常に複雑です。あなたは簡単に猫を書くことができます

main = do
    files <- getArgs
    forM_ files $ \filename -> do
        contents <- readFile filename
        putStr contents

レイジーI/Oのため、ファイルの内容全体は実際にはメモリにロードされませんが、stdoutにストリーミングされます。

からの演算子に慣れている場合はControl.Monad、プログラム全体を次のように短縮できます。

main = getArgs >>= mapM_ (readFile >=> putStr)
于 2012-07-13T17:21:46.697 に答える
17

非常に役立つconduitパッケージをインストールする場合は、次のように実行できます。

module Main where

import Control.Monad
import Data.Conduit
import Data.Conduit.Binary
import System.Environment
import System.IO

main :: IO ()
main = do files <- getArgs
          forM_ files $ \filename -> do
            runResourceT $ sourceFile filename $$ sinkHandle stdout

これは、shangが提案した単純な解決策に似ていますがByteString、遅延I/Oおよびの代わりにコンジットを使用しStringます。これらは両方とも避けることを学ぶのに良いことです。怠惰なI/Oは予測できない時間にリソースを解放します。String多くのメモリオーバーヘッドがあります。

ByteStringこれは、テキストではなく、バイナリデータを表すことを目的としていることに注意してください。この場合、ファイルを解釈されていないバイトシーケンスとして扱っているだけなので、ByteString使用しても問題ありません。OTOHがファイルをテキストとして処理している場合(文字数のカウント、解析など)、を使用しますData.Text

編集:次のように書くこともできます:

main :: IO ()
main = getArgs >>= catFiles

type Filename = String

catFiles :: [Filename] -> IO ()
catFiles files = runResourceT $ mapM_ sourceFile files $$ sinkHandle stdout

オリジナルでは、指定されたファイルから読み取るをsourceFile filename作成します。SourceそしてforM_、外側で各引数をループし、ResourceT各ファイル名に対して計算を実行するために使用します。

ただし、コンジットでは、モナディックを使用>>してソースを連結できます。は、完了するまでsource1 >> source2の要素を生成し、次にの要素を生成するソースです。したがって、この2番目の例では、すべてのソースを連結する—aと同等です。source1source2mapM_ sourceFile filessourceFile file0 >> ... >> sourceFile filenSource

編集2:そしてこの答えへのコメントでダンバートンの提案に従って:

module Main where

import Control.Monad
import Control.Monad.IO.Class
import Data.ByteString
import Data.Conduit
import Data.Conduit.Binary
import System.Environment
import System.IO

main :: IO ()
main = runResourceT $ sourceArgs $= readFileConduit $$ sinkHandle stdout

-- | A Source that generates the result of getArgs.
sourceArgs :: MonadIO m => Source m String
sourceArgs = do args <- liftIO getArgs
                forM_ args yield

type Filename = String          

-- | A Conduit that takes filenames as input and produces the concatenated 
-- file contents as output.
readFileConduit :: MonadResource m => Conduit Filename m ByteString
readFileConduit = awaitForever sourceFile

英語でsourceArgs $= readFileConduitは、はコマンドライン引数で指定されたファイルの内容を生成するソースです。

于 2012-07-13T19:23:57.760 に答える
5

catHandleから間接的に呼び出される、は、最初のファイルの終わりに達したときにcatFileArray呼び出します。exitWithこれによりプログラムが終了し、それ以上のファイルは読み取られなくなります。

catHandle代わりに、ファイルの終わりに達したときに関数から通常どおりに戻る必要があります。これはおそらくあなたが読書をするべきではないことを意味しますforever

于 2012-07-13T17:16:55.460 に答える
5

私の最初のアイデアはこれです:

import System.Environment
import System.IO
import Control.Monad
main = getArgs >>= mapM_ (\name -> readFile name >>= putStr)

それは実際にはUNIXのように失敗することはなく、stdinもマルチバイトのこともしませんが、それは「はるかに面倒」なので、それを共有したかっただけです。それが役に立てば幸い。

一方、putStrはファイルの読み取り中に文字列をすでに空にすることができるため、メモリをいっぱいにすることなく大きなファイルを簡単に処理できるはずです。

于 2012-07-13T17:29:50.927 に答える