2012-07-13 38 views
12

मैं हास्केल में एक साधारण cat प्रोग्राम लिखने की कोशिश कर रहा हूं। मैं कई फ़ाइल नामों को तर्क के रूप में लेना चाहता हूं, और STDOUT पर प्रत्येक फ़ाइल को अनुक्रमिक रूप से लिखना चाहता हूं, लेकिन मेरा प्रोग्राम केवल एक फ़ाइल प्रिंट करता है और बाहर निकलता है।मैं हास्केल में 'बिल्ली' को कैसे कार्यान्वित करूं?

मेरे कोड को प्रत्येक फ़ाइल को मुद्रित करने के लिए मुझे क्या करने की ज़रूरत है, न केवल पहले व्यक्ति में?

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 

उत्तर

16

आपकी समस्या यह है कि exitWith पूरे कार्यक्रम को समाप्त करता है। इसलिए, आप फ़ाइल के माध्यम से लूप करने के लिए वास्तव में forever का उपयोग नहीं कर सकते हैं, क्योंकि स्पष्ट रूप से आप फ़ाइल के अंत तक, "हमेशा के लिए" फ़ंक्शन को चलाने के लिए नहीं चाहते हैं। आप इस

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

यानी की तरह catHandle पुनर्लेखन कर सकते हैं अगर हम ईओएफ तक नहीं पहुंचे हैं, तो हम दूसरी लाइन की मरम्मत और पढ़ते हैं।

हालांकि, यह पूरा दृष्टिकोण अत्यधिक जटिल है। आप बिल्ली बस के रूप में

main = do 
    files <- getArgs 
    forM_ files $ \filename -> do 
     contents <- readFile filename 
     putStr contents 
क्योंकि आलसी की

आई/ओ, पूरी फ़ाइल सामग्री वास्तव में मेमोरी में लोड नहीं कर रहे हैं बारे में है, लेकिन stdout में ही स्ट्रीम कर सकते हैं।

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

यह वास्तव में यूनिक्स में विफल नहीं हुआ:

आप Control.Monad से ऑपरेटरों के साथ सहज महसूस कर रहे हैं, तो पूरे कार्यक्रम

main = getArgs >>= mapM_ (readFile >=> putStr) 
+0

मैंने आपके स्वीकृत उत्तर को स्विच कर दिया क्योंकि आपने मेरी बग तय की और आलसी आईओ स्ट्रीमिंग को भी समझाया। – Sam

+0

आप कैसे कहते हैं '> => '? – Sam

+0

"क्लेस्ली संरचना"। मुझे इसके लिए कोई बेहतर (छोटा) नाम नहीं पता है। – shang

5

catHandle, जो परोक्ष रूप से catFileArray से कहा जाता है, कहता है exitWith जब वह पहली बार फ़ाइल के अंत तक पहुँचता है। यह प्रोग्राम को समाप्त करता है, और आगे की फाइलें और नहीं पढ़ी जाती हैं।

फ़ाइल के अंत तक पहुंचने पर आपको आमतौर पर catHandle फ़ंक्शन से सामान्य रूप से वापस जाना चाहिए। इसका शायद मतलब है कि आपको forever पढ़ना नहीं चाहिए।

+0

आह, मिल गया, धन्यवाद! – Sam

4

मेरा पहला विचार करने के लिए नीचे छोटा किया जा सकता है - वैसे, और stdin और न ही multibyte सामान नहीं करता है, लेकिन यह "रास्ता और अधिक हैकेल" है तो मैं बस इसे साझा करना चाहता था। आशा करता हूँ की ये काम करेगा।

दूसरी ओर, मुझे लगता है कि इसे मेमोरी भरने के बिना आसानी से बड़ी फ़ाइलों को संभालना चाहिए, इस तथ्य के कारण कि putStr फ़ाइल पढ़ने के दौरान स्ट्रिंग को पहले ही खाली कर सकता है।

+0

+1, धन्यवाद। – Sam

17

आप बहुत मददगार conduit package स्थापित हैं, तो आप इसे इस तरह कर सकते हैं:

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 

यह सरल समाधान का सुझाव दिया शांग के लिए इसी तरह की है, लेकिन नाली और आलसी मैं/हे और String के बजाय ByteString का उपयोग कर। उन दोनों से बचने के लिए सीखने के लिए अच्छी चीजें हैं: आलसी I/O अप्रत्याशित समय पर संसाधनों को मुक्त करता है; String में बहुत सारी मेमोरी ओवरहेड है।

ध्यान दें कि ByteString का उद्देश्य बाइनरी डेटा का प्रतिनिधित्व करना है, पाठ नहीं।इस मामले में हम केवल बाइट्स के अनियंत्रित अनुक्रमों के रूप में फ़ाइलों का इलाज कर रहे हैं, इसलिए ByteString उपयोग करने के लिए ठीक है। OTOH अगर हम पात्रों -counting रूप पाठ फ़ाइल को संसाधित कर रहे थे, पार्स करने, आदि-we'd 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 गणना चलाते हैं।

हालांकि नाली में आप स्रोतों को श्रेणीबद्ध करने के लिए monadic >> उपयोग कर सकते हैं; source1 >> source2 एक ऐसा स्रोत है जो source1 के तत्वों को तब तक उत्पन्न करता है जब तक यह पूरा नहीं हो जाता है, फिर source2 का उत्पादन करता है। तो इस दूसरे उदाहरण में, mapM_ sourceFile filessourceFile file0 >> ... >> sourceFile filen -a Source के समतुल्य है जो सभी स्रोतों को जोड़ता है।

संपादित करें 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 एक स्रोत है कि आदेश पंक्ति तर्क द्वारा नामित फ़ाइलों की सामग्री का उत्पादन होता है।

+3

सादगी और लालित्य के लिए एक उत्कृष्ट प्रमाण पत्र +1 करता है जिसे 'कंड्यूट' हासिल किया गया है। मुझे आश्चर्य है कि क्या 'getArgs'-esque स्रोत का उपयोग किया जाएगा। तो फिर तुम लिख सकता है 'runResourceT $ sourceArgs $ = readFileConduit $$ sinkHandle stdout' जहां' sourceArgs :: MonadIO मीटर => स्रोत मीटर स्ट्रिंग' और 'readFileConduit :: MonadResource मीटर => नाली फ़ाइल का नाम मीटर ByteString' –

+0

@DanBurton: मैं अभी भी कंडिशन सीखना, इसलिए मैंने इस पर अपना हाथ लगाने का फैसला किया- और 10 मिनट के भीतर सफल हुआ। मैं उस संस्करण को जोड़ने के लिए प्रतिक्रिया संपादित करूंगा। –

+0

यह तकनीकी रूप से मेरे प्रश्न का उत्तर नहीं है, लेकिन यह इतना जानकारीपूर्ण है कि इसे हास्केल के बारे में समान प्रश्न वाले किसी के लिए "आवश्यक पढ़ने" पर विचार किया जाएगा। – Sam

संबंधित मुद्दे