zoukankan      html  css  js  c++  java
  • [Real World Haskell翻译]第20章 Haskell系统编程

    第20章 Haskell系统编程
    
    到目前为止,我们已经讨论了大多数的高层次的概念。Haskell也可以用于较低级别的系统编程。很可能是用haskell编写出底层的与操作系统接口的程序。
    在本章中,我们将尝试一些雄心勃勃的事情:类似Perl的“语言”,纯用haskell来实现是可行的,它使得shell脚本编程容易。我们要实现管道,简单的命令调用,和一些简单的工具可能会和grep和sed一起处理任务。
    针对不同的操作系统特定的模块是存在的。在本章中,我们将使用尽可能的通用的独立于OS的模块。然而,我们在本章的大部分将重点关注POSIX环境。 POSIX是类Unix操作系统的标准,如Linux,FreeBSD,MacOS X,或Solaris操作系统。 Windows不默认支持POSIX,但Cygwin环境为Windows提供了POSIX兼容层。
    
    运行外部程序
    
    从Haskell调用外部命令是可能的。要做到这一点,我们建议您使用rawSystem来自System.Cmd模块。这将调用指定的程序,用指定的参数,并返回该程序的退出代码。你可以用它玩在ghci中:
    
    ghci> :module System.Cmd
    ghci> rawSystem "ls" ["-l", "/usr"]
    Loading package old-locale-1.0.0.0 ... linking ... done.
    Loading package old-time-1.0.0.0 ... linking ... done.
    Loading package filepath-1.1.0.0 ... linking ... done.
    Loading package directory-1.0.0.1 ... linking ... done.
    Loading package unix-2.3.0.1 ... linking ... done.
    Loading package process-1.0.0.1 ... linking ... done.
    total 408
    drwxr-xr-x 2 root root 94208 2008-08-22 04:51 bin
    drwxr-xr-x 2 root root 4096 2008-04-07 14:44 etc
    drwxr-xr-x 2 root root 4096 2008-04-07 14:44 games
    drwxr-xr-x 155 root root 16384 2008-08-20 20:54 include
    drwxr-xr-x 4 root root 4096 2007-11-01 21:31 java
    drwxr-xr-x 6 root root 4096 2008-03-18 11:38 kerberos
    drwxr-xr-x 70 root root 36864 2008-08-21 04:52 lib
    drwxr-xr-x 212 root root 126976 2008-08-21 04:53 lib64
    drwxr-xr-x 23 root root 12288 2008-08-21 04:53 libexec
    drwxr-xr-x 15 root root 4096 2008-04-07 14:44 local
    drwxr-xr-x 2 root root 20480 2008-08-21 04:53 sbin
    drwxr-xr-x 347 root root 12288 2008-08-21 11:01 share
    drwxr-xr-x 5 root root 4096 2008-04-07 14:44 src
    lrwxrwxrwx 1 root root 10 2008-05-16 15:01 tmp -> ../var/tmp
    drwxr-xr-x 2 root root 4096 2007-04-10 11:01 X11R6
    ExitSuccess
    
    在这里,我们运行shell命令ls -l /usr。 rawSystem无法解析来自字符串或扩展掩码的参数。相反,它希望每一个参数包含在列表中。如果你不想传递任何参数,你可以简单地传递像这样的空列表:
    
    ghci> rawSystem "ls" []
    calendartime.ghci modtime.ghci rp.ghci RunProcessSimple.hs
    cmd.ghci posixtime.hs rps.ghci timediff.ghci
    dir.ghci rawSystem.ghci RunProcess.hs time.ghci
    ExitSuccess
    
    %还有一个的函数system,它只需要一个单一字符串并将其传递给shell解析。我们推荐使用rawSystem,因为shell给某些字符附加了特殊意义,这可能会导致安全问题或意外行为。
    
    目录和文件信息
    
    System.Directory模块包含相当多的函数,它们可以被用来从文件系统的获得信息。你可以得到一个目录中的文件列表,重命名或删除文件,拷贝文件,改变当前工作目录,或创建新的目录。System.Directory是可移植的,它工作在GHC本身工作的任何平台上。
    System.Directory的库参考(http://www.haskell.org/ghc/docs/latest/html/libraries/base/System-Directory.html)提供了可用的函数的完整列表。让我们使用ghci来演示一些。大部分函数直接等同于C库调用或者shell命令:
    
    ghci> :module System.Directory
    ghci> setCurrentDirectory "/etc"
    Loading package old-locale-1.0.0.0 ... linking ... done.
    Loading package old-time-1.0.0.0 ... linking ... done.
    Loading package filepath-1.1.0.0 ... linking ... done.
    Loading package directory-1.0.0.1 ... linking ... done.
    ghci> getCurrentDirectory
    "/etc"
    ghci> setCurrentDirectory ".."
    ghci> getCurrentDirectory
    "/"
    
    这里,我们看到了改变当前的工作目录,取得目前的工作目录的函数。这些类似于POSIX shell中的cd和pwd命令:
    
    ghci> getDirectoryContents "/"
    ["dev",".vmware","mnt","var","etc","net","..","lib","srv","media","lib64","opt",
    ".ccache","bin","selinux",".","lost+found","proc",".autorelabel",".autofsck",
    "sys","misc","home","tmp","boot",".bash_history","root","sbin","usr"]
    
    getDirectoryContents返回给定目录的每个条目的列表。请注意,在POSIX系统,这个列表通常包括特殊值“.”和“..”。当处理目录内容时你通常希望过滤掉这些,或许像这样:
    
    ghci> getDirectoryContents "/" >>= return . filter (`notElem` [".", ".."])
    ["dev",".vmware","mnt","var","etc","net","lib","srv","media","lib64","opt",
    ".ccache","bin","selinux","lost+found","proc",".autorelabel",".autofsck",
    "sys","misc","home","tmp","boot",".bash_history","root","sbin","usr"]
    
    %对于getDirectory内容过滤的结果的更详细的讨论,请参阅第8章。
    %过滤器(`notElem` [".", ".."])令人迷惑?它也可以这样写成(c -> not $ elem c [".", ".."])。在这种情况下,反引号让我们有效地传递第二个参数给notElem,见第76页上的“Infix Function”获取更多的信息。
    
    您也可以在系统中查询某些目录的位置。这个查询会从底层操作系统获取信息:
    
    ghci> getHomeDirectory
    "/home/bos"
    ghci> getAppUserDataDirectory "myApp"
    "/home/bos/.myApp"
    ghci> getUserDocumentsDirectory
    "/home/bos"
    
    程序终止
    
    开发人员经常编写单独的程序来完成特定的任务。也可以组合这些单独的部件来完成大的任务。一个shell脚本或其他程序可能会执行它们。调用脚本经常需要一个发现这个程序是否能够顺利完成其任务的方法。haskell会自动显示一个非正常退出,每当一个程序被异常中止时。
    但是,您可能需要比退出代码更细粒度的控制。也许您需要对不同的错误类型返回不同的代码。 System.Exit模块提供了退出程序并返回特定的退出状态代码给调用者的方法。
    您可以调用exitWith ExitSuccess来返回一个表示成功终止(0 on POSIX systems)的代码。或者,你可以调用类似的 exitWith (ExitFailure 5),它调用程序将返回代码5。
    
    日期和时间
    
    文件中的时间戳对于商业交易涉及日期和时间。Haskell提供了操作日期和时间以及从系统中获取日期和时间信息的方法。
    
    ClockTime和CalendarTime
    
    在Haskell中,System.Time模块主要负责日期和时间处理。它定义了两种类型:ClockTime和CalendarTime。
    ClockTime是传统POSIX时代的haskell版本。ClockTime表示相对于1970年1月1日凌晨的时间,Coordinated Universal Time(UTC)。负ClockTime表示在此日期之前的秒数,而一个正数表示此日期后的秒数。
    ClockTime便于计算。由于它追随UTC,它并不需要调整,对当地时区,夏令时,或其他特殊情况。每天是确切的(60 * 60 *24)或86,400秒,这使得时间间隔的计算简化。例如,您可以在长任务的开始检查ClockTime,在最后也进行检查,简单地从结束时间减去开始时间来决定多少时间流逝了。如果你愿意,你可以再除以3600来显示经过时间的小时数。
    
    ClockTimeis适合回答这些问题:
    •已经过去了多少时间?
    •这个精确时刻的14天前的ClockTime是什么?
    •什么时候文件被最后修改?
    •现在的精确的时间是?
    ClockTime的使用是很好的,因为它是准确的,时间上是清晰的。然而,ClockTime不易使用于如下的问题:
    •今天是星期一吗?
    •明年的5月1日是星期几?
    •本地时区的当前时间是什么,考虑到Daylight Saving Time(DST)呢?
    
    CalendarTime存储时间的方式和人类相同:年,月,日,小时,分钟,秒,时区和DST信息。人们很容易将它转换成一个方便显示的字符串,或回答当地时间的问题。
    您可以在ClockTime和CalendarTime之间转换。haskell包括将ClockTime转换到本地时区的CalendarTime或表现为UTC的CalendarTime的函数。
    
    使用ClockTime
    
    ClockTime被定义在System.Time中,像这样:
    
    data ClockTime = TOD Integer Integer
    
    第一个整数表示自1970年那个时刻以来的秒数。第二个整数表示附加的皮秒数。因为haskell中的ClockTime使用无界的Integer类型,它可以有效地表示一个日期范围只受限于计算资源。
    让我们来看看一些使用ClockTime的方式。首先,有个getClockTime函数根据系统时钟返回当前时间:
    
    ghci> :module System.Time
    ghci> getClockTime
    Loading package old-locale-1.0.0.0 ... linking ... done.
    Loading package old-time-1.0.0.0 ... linking ... done.
    Sat Aug 23 22:30:03 PDT 2008
    
    如果你等待一秒并再次运行getClockTime,它会返回一个更新后的时间。注意命令行的输出是一个非常漂亮的字符串,具有星期的信息。这就是由于ClockTime是Show的实例。让我们在底层看看ClockTime:
    
    ghci> TOD 1000 0
    Wed Dec 31 16:16:40 PST 1969
    ghci> getClockTime >>= ((TOD sec _) -> return sec)
    1219555803
    
    在这里,我们首先构造一个代表1970年1月1日凌晨后1000秒的ClockTime。那个时刻被称为划epoch。根据您的时区,这一刻可能对应到1969年12月31日晚上。
    在第二个例子中,我们从getClockTime返回的值中拉出秒数。现在,我们可以操纵它,像这样:
    
    ghci> getClockTime >>= ((TOD sec _) -> return (TOD (sec + 86400) 0))
    Sun Aug 24 22:30:03 PDT 2008
    
    这将显示在您的本地时区从现在开始的恰好的24小时的时间,因为24小时有86,400秒。
    
    使用CalendarTime
    
    正如它的名字所暗示的,CalendarTime表示了像我们日历上的时间。它有如年,月,日这样的信息字段。CalendarTime及其相关的类型是这样定义的:
    
    data CalendarTime = CalendarTime
       {ctYear :: Int, -- Year (post-Gregorian)
        ctMonth :: Month, 
        ctDay :: Int, -- Day of the month (1 to 31)
        ctHour :: Int, -- Hour of the day (0 to 23)
        ctMin :: Int, -- Minutes (0 to 59)
        ctSec :: Int, -- Seconds (0 to 61, allowing for leap seconds)
        ctPicosec :: Integer, -- Picoseconds
        ctWDay :: Day, -- Day of the week
        ctYDay :: Int, -- Day of the year (0 to 364 or 365)
        ctTZName :: String, -- Name of timezone
        ctTZ :: Int, -- Variation from UTC in seconds
        ctIsDST :: Bool -- True if Daylight Saving Time in effect
       }
    
    data Month = January | February | March | April | May | June 
                 | July | August | September | October | November | December
    
    data Day = Sunday | Monday | Tuesday | Wednesday
               | Thursday | Friday | Saturday 
    
    有关这些结构,有几件事情应该强调:
    •ctWDay,ctYDay,ctTZName是由创建CalendarTime的库函数生成的,但不用于计算。如果您要手动创建一个CalendarTime,没有必要把精确的值放入这些字段,除非你稍后的计算将依赖他们。
    •所有这三种类型都是EQ,Ord,Read,和Show类型类的成员。此外,Month和Day被声明为Enum和Bounded类型类的成员。对于这些类型类的更多信息,请参阅第139页“Important Build-in Typeclasses“。
    您可以有几种方法生成CalendarTime。你可以通过转换一个ClockTime为一个CalendarTime开始,像这样:
    
    ghci> :module System.Time
    ghci> now <- getClockTime
    Loading package old-locale-1.0.0.0 ... linking ... done.
    Loading package old-time-1.0.0.0 ... linking ... done.
    Sat Aug 23 22:29:59 PDT 2008
    ghci> nowCal <- toCalendarTime now
    CalendarTime {ctYear = 2008, ctMonth = August, ctDay = 23, ctHour = 22, 
    ctMin = 29,ctSec = 59, ctPicosec = 877577000000, ctWDay = Saturday, 
    ctYDay = 235, ctTZName ="PDT", ctTZ = -25200, ctIsDST = True}
    ghci> let nowUTC = toUTCTime now
    ghci> nowCal
    CalendarTime {ctYear = 2008, ctMonth = August, ctDay = 23, ctHour = 22, 
    ctMin = 29, ctSec = 59, ctPicosec = 877577000000, ctWDay = Saturday, 
    ctYDay = 235, ctTZName = "PDT", ctTZ = -25200, ctIsDST = True}
    ghci> nowUTC
    CalendarTime {ctYear = 2008, ctMonth = August, ctDay = 24, ctHour = 5, 
    ctMin = 29, ctSec = 59, ctPicosec = 877577000000, ctWDay = Sunday, 
    ctYDay = 236, ctTZName = "UTC", ctTZ = 0, ctIsDST = False}
    
    我们使用getClockTime从系统的时钟取得当前的ClockTime。接下来,toCalendarTime将ClockTime转换为代表本地时区的时间的CalendarTime。 toUTCtime执行类似的转换,但其结果是UTC时区,而不是本地时区。
    请注意,toCalendarTime是一个IO函数,但toUTCTime不是。原因是 toCalendarTime返回一个依赖于本地配置的时区的不同的结果,但传递相同的源ClockTime时,toUTCTime将返回完全相同的结果。
    很容易修改CalendarTime值:
    
    ghci> nowCal {ctYear = 1960}
    CalendarTime {ctYear = 1960, ctMonth = August, ctDay = 23, 
    ctHour = 22, ctMin = 29, ctSec = 59, ctPicosec = 877577000000,
    ctWDay = Saturday, ctYDay = 235, ctTZName = "PDT", 
    ctTZ = -25200, ctIsDST = True}
    ghci> ((TOD sec _) -> sec) (toClockTime nowCal)
    1219555799
    ghci> ((TOD sec _) -> sec) (toClockTime (nowCal {ctYear = 1960}))
    -295209001
    
    在这个例子中,我们首先取得CalendarTime的早期的值并简单地转换它的年份为1960年。然后,我们使用toClockTime转换未经修改的值为ClockTime,然后修改值,所以你可以看到其中的差别。请注意,一旦转换为ClockTime修改后的值将显示为秒的负数。这是可以预料到的,因为ClockTime是从1970年1月1日凌晨的偏移,而这个值是在1960年。
    您也可以手动创建CalendarTime值​​:
    
    ghci> let newCT = CalendarTime 2010 January 15 12 30 0 0 Sunday 0 "UTC" 0 False
    ghci> newCT
    CalendarTime {ctYear = 2010, ctMonth = January, ctDay = 15, ctHour = 12, 
    ctMin = 30, ctSec = 0, ctPicosec = 0, ctWDay = Sunday, ctYDay = 0, 
    ctTZName = "UTC", ctTZ = 0, ctIsDST = False}
    ghci> ((TOD sec _) -> sec) (toClockTime newCT)
    1263558600
    
    需要注意的是,尽管2010年1月15日,不是星期日,不是一年中的第0天,系统能够处理得刚好。事实上,如果我们把值转换为ClockTime然后再转换回CalendarTime,你会发现这些字段正确填充:
    
    ghci> toUTCTime . toClockTime $ newCT
    CalendarTime {ctYear = 2010, ctMonth = January, ctDay = 15, ctHour = 12, 
    ctMin = 30, ctSec = 0, ctPicosec = 0, ctWDay = Friday, ctYDay = 14, 
    ctTZName = "UTC", ctTZ = 0, ctIsDST = False}
    
    ClockTime中的TimeDiff
    
    因为它难以以人们友好的方法管理ClockTime值间的不同​​,System.Time模块包括TimeDiff类型。 在方便的情况下,TimeDiff可以被使用来处理这些差异。它的定义是这样的:
    
    data TimeDiff = TimeDiff
       {tdYear :: Int,
        tdMonth :: Int,
        tdDay :: Int,
        tdHour :: Int,
        tdMin :: Int,
        tdSec :: Int,
        tdPicosec :: Integer}
    
    函数如diffClockTimes和addToClockTime取参数ClockTime和TimeDiff并通过转换为CalendarTime在内部进行计算,以适应差异,并转换回ClockTime。 
    让我们来看看它是如何工作的:
    
    ghci> :module System.Time
    ghci> let feb5 = toClockTime $ CalendarTime 2008 February 5 0 0 0 0 Sunday 0
    "UTC" 0 False
    Loading package old-locale-1.0.0.0 ... linking ... done.
    Loading package old-time-1.0.0.0 ... linking ... done.
    ghci> feb5
    Mon Feb 4 16:00:00 PST 2008
    ghci> addToClockTime (TimeDiff 0 1 0 0 0 0 0) feb5
    Tue Mar 4 16:00:00 PST 2008
    ghci> toUTCTime $ addToClockTime (TimeDiff 0 1 0 0 0 0 0) feb5
    CalendarTime {ctYear = 2008, ctMonth = March, ctDay = 5, ctHour = 0,
    ctMin = 0, ctSec = 0, ctPicosec = 0, ctWDay = Wednesday, ctYDay = 64, 
    ctTZName = "UTC", ctTZ = 0, ctIsDST = False}
    ghci> let jan30 = toClockTime $ CalendarTime 2009 January 30 0 0 0 0 
    Sunday 0 "UTC" 0 False
    ghci> jan30
    Thu Jan 29 16:00:00 PST 2009
    ghci> addToClockTime (TimeDiff 0 1 0 0 0 0 0) jan30
    Sun Mar 1 16:00:00 PST 2009
    ghci> toUTCTime $ addToClockTime (TimeDiff 0 1 0 0 0 0 0) jan30
    CalendarTime {ctYear = 2009, ctMonth = March, ctDay = 2, ctHour = 0, ctMin = 0, 
    ctSec = 0, ctPicosec = 0, ctWDay = Monday, ctYDay = 60, ctTZName = "UTC", ctTZ = 
    0, ctIsDST = False}
    ghci> diffClockTimes jan30 feb5
    TimeDiff {tdYear = 0, tdMonth = 0, tdDay = 0, tdHour = 0, tdMin = 0, tdSec = 31104000, 
    tdPicosec = 0}
    ghci> normalizeTimeDiff $ diffClockTimes jan30 feb5
    TimeDiff {tdYear = 0, tdMonth = 12, tdDay = 0, tdHour = 0, tdMin = 0, tdSec = 0, 
    tdPicosec = 0}
    
    我们通过生成代表2008年2月5日午夜UTC的ClockTime开始。需要注意的是,除非您的时区和UTC相同,当时间在显示器上打印输出,它可能会显示为2月4日晚,因为它对于你的当地时区进行了格式化。
    接下来,我们通过调用addToClockTime增加一个月给它。 2008年是一个闰年,但系统妥善处理并且我们所得到的结果具有相同的三月中的日期和时间 。使用toUTCTime,我们可以看到原来的UTC时区在这个问题上的影响。 
    对于第二个实验,我们设置了代表2009年1月30日午夜UTC的时间。2009年不是闰年,所以我们可能想知道会发生什么,当试图给它加1个月。我们可以看到,因为无论是2月29日或30日都不存在于2009年,我们得到了3月2日。
    最后,我们可以看到diffClockTimes如何转换两个ClockTime值​到一个timeDiff中,虽然只是秒和皮秒被填充。normalizeTimeDiff函数需要这样的TimeDiff和重新把它格式化为人们希望看到的形式。
    
    文件修改时间 
    
    许多程序需要找出特定文件的最后修改的时间。程序如ls或图形文件管理器通常会显示文件的修改时间。 System.Directory模块包含一个的跨平台getModificationTime函数。它需要一个文件名并返回一个代表文件的最后修改的时间的ClockTime。实例:
    
    ghci> :module System.Directory
    ghci> getModificationTime "/etc/passwd"
    Loading package old-locale-1.0.0.0 ... linking ... done.
    Loading package old-time-1.0.0.0 ... linking ... done.
    Loading package filepath-1.1.0.0 ... linking ... done.
    Loading package directory-1.0.0.1 ... linking ... done.
    Mon Jul 14 04:06:29 PDT 2008
    
    POSIX平台上不只维护一个修改时间(称为mtime),也维护最后读或写的访问时间(atime)和最近状态改变时间(ctime)。由于这个信息是POSIX特有的,跨平台System.Directory模块不提供对它的访问。作为代替,你可以使用System.Posix.Files中的函数。下面是一个示例函数:
    
    -- file: ch20/posixtime.hs
    -- posixtime.hs
    
    import System.Posix.Files
    import System.Time
    import System.Posix.Types
    
    -- | Given a path, returns (atime, mtime, ctime)
    getTimes :: FilePath -> IO (ClockTime, ClockTime, ClockTime)
    getTimes fp =
        do stat <- getFileStatus fp
           return (toct (accessTime stat),
                   toct (modificationTime stat),
                   toct (statusChangeTime stat))
    
    -- | Convert an EpochTime to a ClockTime
    toct :: EpochTime -> ClockTime
    toct et = 
        TOD (truncate (toRational et)) 0
    
    注意,getFileStatus的调用直接对应到到C函数stat()。其返回值存储大量的各类的信息,包括文件类型,权限,所有者,组和我们感兴趣的System.Posix.Files中的三个时间值,如accessTime,它从不透明的由getFileStatus返回的FileStatus类型中提取我们感兴趣的信息。
    函数如accessTime返回叫做EpochTime的POSIX特定类型的数据,它使用toct函数转换为ClockTime。System.Posix.Files也提供了一个setFileTimes函数来为文件设置atime和mtime。
    
    扩展的例子:管道
    
    我们刚刚看到了如何调用外部程序。有时候,我们需要更多的控制。也许我们需要获得其它程序的输出,作为输入,甚至链接多个外部程序。所有这些需求管道可以帮助。管道经常被用在shell脚本中。当您在shell中设置了管道,你跑多个程序。第一个程序的输出发送到第二个的输入。第二个的输出发送到第三个的输入,等等。最后一个程序输出到终端,或者送入一个文件。下面是一个说明管道的POSIX shell的示例会话:
    
    $ ls /etc | grep 'm.*ap' | tr a-z A-Z
    IDMAPD.CONF
    MAILCAP
    MAILCAP.ORDER
    MEDIAPRM
    TERMCAP
    
    此命令会运行三个程序,它们之间通过管道传递数据。它以ls /etc开始,它输出/etc中的所有文件或目录的列表。ls的输出作为输入发送给grep。我们给grep一个正则表达式,这将导致它仅输出以'm'开始然后包含"ap"的行。最后的结果发送给tr。 我们给tr选项来把一切转换为大写。tr的输出没有特别设置,所以它被显示在屏幕上。
    在这种情况下,shell设置了程序之间所有的管道。通过使用一些POSIX工具在Haskell中,我们可以完成同样的事情。
    描述如何做到这一点之前,我们应该先提醒你System.Posix模块提供了一些非常底层Unix系统接口。接口和它们之间的交互可以很复杂,不管您使用来访问它们的编程语言。这些底层接口的全部性质的话题是整本书的主题,因此我们只会在本章涉及表面。
    
    使用管道重定向
    
    POSIX定义了创建管道的函数。这个函数返回两个文件描述符(FDs) ,这和Haskell Handle是类似的概念。一个FD是管道的读端,而另一个是写端。任何写入写端的东西都由读端读出。数据是通过管道传送。在Haskell中,你调用createPipe访问此接口。
    有一个管道,是能够用管道在外部程序之间传送数据的第一步。我们还必须能将一个程序的输出重定向到另一个的输入。haskell函数dupTo完成这项工作。它取得一个FD并在另一个FD number得到它的一个副本。 标准输入,标准输出和标准错误的POSIX FDs有预定义的FD number,分别为0,1,和2。通过给管道的一个端点重新分配一个number,我们可以有效地引起程序的输入或输出重定向。
    这里是问题的另一方面。我们不能在一个调用如rawSystem之前仅仅使用dupTo,因为那会搞乱我们的主要haskell进程的标准输入或输出。此外,rawSystem会阻塞直到被调用程序执行,使得我们没有办法启动多个进程并行运行。要做到这一点,我们必须使用forkProcess。这是一个非常特殊的函数。它实际上是当前正在运行的程序的一个副本,我们让两个副本在同一时间运行。 Haskell的forkProcess函数需要一个函数在新的进程中执行(称为孩子)。我们有函数调用dupTo。它做完之后,它调用executeFile来实际调用命令。这也是一个特殊的函数:如果一切顺利的话,它永远不会返回。这是因为executeFile以不同的程序代替了正在运行的进程。最终,原始的Haskell程序会调用getProcessStatus来等待子进程终止并得到他们的退出代码。
    每当你运行一个命令在POSIX系统上,无论何时你在命令行输入ls或在Haskell中使用rawSystem,在表面之下,forkProcess,executeFile,getProcessStatus(或等价的C函数)总是被使用。为了设置管道,我们复制了系统用于启动程序的进程,并添加几个步骤用于管道和重定向。
    有其他几个关键的事情我们必须当心。当您调用forkProcess,几乎关于你的程序的一切被克隆。这包括打开的文件描述符(handles)的集合。程序通过检查文件结束标志检测他们从一个管道的输入完成接收的时间。当进程在管道的写端关闭管道,读端的进程将收到一个文件结束标志。但是,如果写文件描述符存在于不只一个进程中,文件结束标志不会被发送直到所有进程都已关闭那个特别的FD。因此,我们必须跟踪哪个文件描述符被打开,这样我们就可以在子进程中关闭它们。我们还必须尽可能在父进程中关闭管道的孩子端。
    这里是Haskell管道系统的初始实现:
    
    -- file: ch20/RunProcessSimple.hs
    {-# OPTIONS_GHC -fglasgow-exts #-}
    -- RunProcessSimple.hs
    
    module RunProcessSimple where
    
    import System.Process
    import Control.Concurrent
    import Control.Concurrent.MVar
    import System.IO
    import System.Exit
    import Text.Regex
    import System.Posix.Process
    import System.Posix.IO
    import System.Posix.Types
    
    {- | The type for running external commands. The first part
    of the tuple is the program name. The list represents the
    command-line parameters to pass to the command. -}
    type SysCommand = (String, [String])
    
    {- | The result of running any command -}
    data CommandResult = CommandResult {
        cmdOutput :: IO String, -- ^ IO action that yields the output
        getExitStatus :: IO ProcessStatus -- ^ IO action that yields exit result
        }
    
    {- | The type for handling global lists of FDs to always close in the clients
    -}
    type CloseFDs = MVar [Fd]
    
    {- | Class representing anything that is a runnable command -}
    class CommandLike a where
        {- | Given the command and a String representing input,
             invokes the command. Returns a String
             representing the output of the command. -}
        invoke :: a -> CloseFDs -> String -> IO CommandResult
    
    -- Support for running system commands
    instance CommandLike SysCommand where
        invoke (cmd, args) closefds input =
            do -- Create two pipes: one to handle stdin and the other
               -- to handle stdout. We do not redirect stderr in this program.
               (stdinread, stdinwrite) <- createPipe
               (stdoutread, stdoutwrite) <- createPipe
    
               -- We add the parent FDs to this list because we always need
               -- to close them in the clients.
               addCloseFDs closefds [stdinwrite, stdoutread]
    
               -- Now, grab the closed FDs list and fork the child.
               childPID <- withMVar closefds (fds ->
                              forkProcess (child fds stdinread stdoutwrite))
    
               -- Now, on the parent, close the client-side FDs.
               closeFd stdinread
               closeFd stdoutwrite
    
               -- Write the input to the command.
               stdinhdl <- fdToHandle stdinwrite
               forkIO $ do hPutStr stdinhdl input
                           hClose stdinhdl
    
               -- Prepare to receive output from the command
               stdouthdl <- fdToHandle stdoutread
    
               -- Set up the function to call when ready to wait for the
               -- child to exit.
               let waitfunc = 
                    do status <- getProcessStatus True False childPID
                       case status of
                           Nothing -> fail $ "Error: Nothing from getProcessStatus"
                           Just ps -> do removeCloseFDs closefds 
                            [stdinwrite, stdoutread]
                                         return ps
               return $ CommandResult {cmdOutput = hGetContents stdouthdl,
                                       getExitStatus = waitfunc}
    
            -- Define what happens in the child process
            where child closefds stdinread stdoutwrite = 
                    do -- Copy our pipes over the regular stdin/stdout FDs
                       dupTo stdinread stdInput
                       dupTo stdoutwrite stdOutput
    
                       -- Now close the original pipe FDs
                       closeFd stdinread
                       closeFd stdoutwrite
    
                       -- Close all the open FDs we inherited from the parent
                       mapM_ (fd -> catch (closeFd fd) (\_ -> return ())) closefds
    
                       -- Start the program
                       executeFile cmd True args Nothing
    
    -- Add FDs to the list of FDs that must be closed post-fork in a child
    addCloseFDs :: CloseFDs -> [Fd] -> IO ()
    addCloseFDs closefds newfds =
        modifyMVar_ closefds (oldfds -> return $ oldfds ++ newfds)
    
    -- Remove FDs from the list
    removeCloseFDs :: CloseFDs -> [Fd] -> IO ()
    removeCloseFDs closefds removethem =
        modifyMVar_ closefds (fdlist -> return $ procfdlist fdlist removethem)
    
        where
        procfdlist fdlist [] = fdlist
        procfdlist fdlist (x:xs) = procfdlist (removefd fdlist x) xs
    
        -- We want to remove only the first occurance ot any given fd
        removefd [] _ = []
        removefd (x:xs) fd 
            | fd == x = xs
            | otherwise = x : removefd xs fd
    
    {- | Type representing a pipe. A 'PipeCommand' consists of a source
    and destination part, both of which must be instances of
    'CommandLike'. -}
    data (CommandLike src, CommandLike dest) => 
        PipeCommand src dest = PipeCommand src dest 
    
    {- | A convenient function for creating a 'PipeCommand'. -}
    (-|-) :: (CommandLike a, CommandLike b) => a -> b -> PipeCommand a b
    (-|-) = PipeCommand
    
    {- | Make 'PipeCommand' runnable as a command -}
    instance (CommandLike a, CommandLike b) =>
             CommandLike (PipeCommand a b) where
        invoke (PipeCommand src dest) closefds input =
            do res1 <- invoke src closefds input
               output1 <- cmdOutput res1
               res2 <- invoke dest closefds output1
               return $ CommandResult (cmdOutput res2) (getEC res1 res2)
    
    {- | Given two 'CommandResult' items, evaluate the exit codes for
    both and then return a "combined" exit code. This will be ExitSuccess
    if both exited successfully. Otherwise, it will reflect the first
    error encountered. -}
    getEC :: CommandResult -> CommandResult -> IO ProcessStatus 
    getEC src dest =
        do sec <- getExitStatus src
           dec <- getExitStatus dest
           case sec of
                Exited ExitSuccess -> return dec
                x -> return x
    
    {- | Execute a 'CommandLike'. -}
    runIO :: CommandLike a => a -> IO ()
    runIO cmd =
        do -- Initialize our closefds list
           closefds <- newMVar []
    
           -- Invoke the command
           res <- invoke cmd closefds []
    
           -- Process its output
           output <- cmdOutput res
           putStr output
    
           -- Wait for termination and get exit status
           ec <- getExitStatus res
           case ec of
                Exited ExitSuccess -> return ()
                x -> fail $ "Exited: " ++ show x
    
    在看它是如何工作之前,让我们在ghci中进行一点实验:
    
    ghci> :load RunProcessSimple.hs
    [1 of 1] Compiling RunProcessSimple ( RunProcessSimple.hs, interpreted )
    Ok, modules loaded: RunProcessSimple.
    ghci> runIO $ ("pwd", []::[String])
    Loading package array-0.1.0.0 ... linking ... done.
    Loading package bytestring-0.9.0.1.1 ... linking ... done.
    Loading package old-locale-1.0.0.0 ... linking ... done.
    Loading package old-time-1.0.0.0 ... linking ... done.
    Loading package filepath-1.1.0.0 ... linking ... done.
    Loading package directory-1.0.0.1 ... linking ... done.
    Loading package unix-2.3.0.1 ... linking ... done.
    Loading package process-1.0.0.1 ... linking ... done.
    Loading package regex-base-0.72.0.1 ... linking ... done.
    Loading package regex-posix-0.72.0.2 ... linking ... done.
    Loading package regex-compat-0.71.0.1 ... linking ... done.
    /home/bos/src/darcs/book/examples/ch20
    ghci> runIO $ ("ls", ["/usr"])
    bin
    etc
    games
    include
    java
    kerberos
    lib
    lib64
    libexec
    local
    sbin
    share
    src
    tmp
    X11R6
    ghci> runIO $ ("ls", ["/usr"]) -|- ("grep", ["^l"])
    lib
    lib64
    libexec
    local
    ghci> runIO $ ("ls", ["/etc"]) -|- ("grep", ["m.*ap"]) -|- ("tr", ["a-z", "A-Z"])
    IDMAPD.CONF
    MAILCAP
    PM-UTILS-HD-APM-RESTORE.CONF
    
    我们通过运行一个简单的命令pwd开始,它只是打印当前工作目录的名字。我们传递参数[]作为列表,因为的pwd不需要任何参数。由于类型类的使用,haskell无法推断[]的类型,所以我们特别指定,这是一个字符串。
    
    然后我们进入更复杂的命令。我们运行ls ,发送它给grep 。最后,我们设置一个管道来运行相同的指令,它是我们在本节开始通过shell内建管道运行的。这还不是愉快的,因为它是在shell,但我们的程序相对shell仍然是比较简单的。
    让我们来看看这个程序。第一行具有一个特殊的OPTIONS_GHC条目。这和传递-fglasgow-exts给ghc或ghci是一样的。我们正在使用一个GHC扩展,它允许我们使用(String, [String])类型作为一个类型类的实例。把它放在源文件中意味着我们不必记得每次使用此模块时指定它。
    import行之后,我们定义了几个类型。首先,我们定义类型别名type SysCommand =(String, [String])。这是系统所要执行的命令的类型。我们在上面的例子中执行的每一个命令使用这种类型的数据。CommandResult类型代表执行一个给定的命令的结果,CloseFDs类型代表我们必须关闭的forking的新的孩子进程的FDS的列表。
    接下来,我们定义了一个名为CommandLike的类,它将被用来运行“things”,“things”可能是一个独立的程序,两个或多个程序之间建立的管道,甚至是纯的Haskell函数在未来。作为这个类的一个成员,只有一个函数,invoke,需要为一个给定的类型呈现。这将让我们用runIO来启动一个独立的命令或管道。这对于定义管道也是有用的,因为我们可能有一个完整的命令栈在给定的命令的一侧或两侧。
    我们的管道作为基础设施将要使用string作为从一个进程向另一个发送数据的方式。我们可以利用Haskell支持读取时通过hGetContents惰性读取的优势,并用forkIO让读取在后台发生。这将工作得很好,虽然没有直接连接两个进程的端点那么快%。但是这使得实现很简单。我们只需要关心整个String被缓存,让Haskell的惰性做剩下的事情。
    
    %Haskell库HSH提供了一个和这里介绍的类似的API,但是它使用一个更有效的(更复杂)
    机制来直接在外部进程之间连接管道不需要传送数据。这是shell采取的同样的方法,它减少了处理管道的CPU负荷。
    
    接下来,我们为SysCommand定义了CommandLike的一个实例。我们创建了两个管道:一个用于新的进程的标准输入,另一个用于标准输出。这创建四个端点和四个文件描述符。我们添加父文件描述符到那些所有子文件描述符必须被关闭的列表中。这些将是子文件描述符的标准输入的写端,和标准输出的读端。接下来,我们fork子进程。在父进程中,我们可以关闭对应的子进程的文件描述符。fork之前,我们不能做这个,因为他们对于子进程不可用。我们取得stdinwrite文件描述符的handle,并通过forkIO启动一个线程来写输入数据给它。We then define  waitfunc, which is the action that the caller will invoke when it is ready to wait for the called process to terminate.同时,子进程使用dupTo,关闭它不需要的文件描述符,并且执行该命令。
    接下来,我们定义了一些实用的函数来管理文件描述符列表。在那之后,我们定义帮助建立管道的工具。首先,我们定义一个新类型PipeCommand,它具有源和目标。源和目标都必须是CommandLike的成员。我们还定义了-|-便捷操作符。然后,我们让PipeCommand成为CommandLike的一个实例。其invoke实现启动了第一个命令与给定的输入,获得其输出,并将其输出传递到第二个命令的调用。然后,它返回的第二个命令的输出,并导致getExitStatus函数等待和检查这两个命令的退出状态。
    我们通过定义runIO完成。建立FDs列表的函数必须在客户端被关闭,启动命令,显示输出,并检查其退出状态。
    
    更好的管道
    
    我们前面的例子解决了让我们建立类shell管道的基本需要。有一些其他的特性会更好:
    •支持更多的类shell的语法
    •让人们通过管道传送数据到外部程序或普通的Haskell函数的能力,自由地混合和匹配这两个。
    •在Haskell程序中返回最终的输出和退出代码的能力
    幸运的是,我们已经有了大多数片段来支持这个在适当的地方。我们只需要添加几个CommandLike的实例来支持这个和几个类似runIO的函数。下面是修改后的例子,实现了所有这些特性:
    
    -- file: ch20/RunProcess.hs
    {-# OPTIONS_GHC -fglasgow-exts #-}
    
    module RunProcess where
    
    import System.Process
    import Control.Concurrent
    import Control.Concurrent.MVar
    import Control.Exception(evaluate)
    import System.Posix.Directory
    import System.Directory(setCurrentDirectory)
    import System.IO
    import System.Exit
    import Text.Regex
    import System.Posix.Process
    import System.Posix.IO
    import System.Posix.Types
    import Data.List
    import System.Posix.Env(getEnv)
    
    {- | The type for running external commands. The first part
    of the tuple is the program name. The list represents the
    command-line parameters to pass to the command. -}
    type SysCommand = (String, [String])
    
    {- | The result of running any command -}
    data CommandResult = CommandResult {
        cmdOutput :: IO String, -- ^ IO action that yields the output
        getExitStatus :: IO ProcessStatus -- ^ IO action that yields exit result
        }
    
    {- | The type for handling global lists of FDs to always close in the clients
    -}
    type CloseFDs = MVar [Fd]
    
    {- | Class representing anything that is a runnable command -}
    class CommandLike a where
        {- | Given the command and a String representing input,
             invokes the command. Returns a String
             representing the output of the command. -}
        invoke :: a -> CloseFDs -> String -> IO CommandResult
    
    -- Support for running system commands
    instance CommandLike SysCommand where
        invoke (cmd, args) closefds input =
            do -- Create two pipes: one to handle stdin and the other
               -- to handle stdout. We do not redirect stderr in this program.
               (stdinread, stdinwrite) <- createPipe
               (stdoutread, stdoutwrite) <- createPipe
    
               -- We add the parent FDs to this list because we always need
               -- to close them in the clients.
               addCloseFDs closefds [stdinwrite, stdoutread]
    
               -- Now, grab the closed FDs list and fork the child.
               childPID <- withMVar closefds (fds ->
               forkProcess (child fds stdinread stdoutwrite))
    
               -- Now, on the parent, close the client-side FDs.
               closeFd stdinread
               closeFd stdoutwrite
    
               -- Write the input to the command.
               stdinhdl <- fdToHandle stdinwrite
               forkIO $ do hPutStr stdinhdl input
                           hClose stdinhdl
    
               -- Prepare to receive output from the command
               stdouthdl <- fdToHandle stdoutread
    
               -- Set up the function to call when ready to wait for the
               -- child to exit.
               let waitfunc = 
                    do status <- getProcessStatus True False childPID
                       case status of
                           Nothing -> fail $ "Error: Nothing from getProcessStatus"
                           Just ps -> do removeCloseFDs closefds 
                                              [stdinwrite, stdoutread]
                                         return ps
               return $ CommandResult {cmdOutput = hGetContents stdouthdl,
                                       getExitStatus = waitfunc}
    
            -- Define what happens in the child process
            where child closefds stdinread stdoutwrite = 
                    do -- Copy our pipes over the regular stdin/stdout FDs
                       dupTo stdinread stdInput
                       dupTo stdoutwrite stdOutput
    
                       -- Now close the original pipe FDs
                       closeFd stdinread
                       closeFd stdoutwrite
    
                       -- Close all the open FDs we inherited from the parent
                       mapM_ (fd -> catch (closeFd fd) (\_ -> return ())) closefds
    
                       -- Start the program
                       executeFile cmd True args Nothing
    
    {- | An instance of 'CommandLike' for an external command. The String is
    passed to a shell for evaluation and invocation. -}
    instance CommandLike String where
        invoke cmd closefds input =
            do -- Use the shell given by the environment variable SHELL,
               -- if any. Otherwise, use /bin/sh
               esh <- getEnv "SHELL"
               let sh = case esh of
                          Nothing -> "/bin/sh"
                          Just x -> x
               invoke (sh, ["-c", cmd]) closefds input
    
    -- Add FDs to the list of FDs that must be closed post-fork in a child
    addCloseFDs :: CloseFDs -> [Fd] -> IO ()
    addCloseFDs closefds newfds =
        modifyMVar_ closefds (oldfds -> return $ oldfds ++ newfds)
    
    -- Remove FDs from the list
    removeCloseFDs :: CloseFDs -> [Fd] -> IO ()
    removeCloseFDs closefds removethem =
        modifyMVar_ closefds (fdlist -> return $ procfdlist fdlist removethem)
    
        where
        procfdlist fdlist [] = fdlist
        procfdlist fdlist (x:xs) = procfdlist (removefd fdlist x) xs
    
        -- We want to remove only the first occurance ot any given fd
        removefd [] _ = []
        removefd (x:xs) fd 
            | fd == x = xs
            | otherwise = x : removefd xs fd
    
    -- Support for running Haskell commands
    instance CommandLike (String -> IO String) where
        invoke func _ input =
           return $ CommandResult (func input) (return (Exited ExitSuccess))
    
    -- Support pure Haskell functions by wrapping them in IO
    instance CommandLike (String -> String) where
        invoke func = invoke iofunc
            where iofunc :: String -> IO String
                  iofunc = return . func
    
    -- It's also useful to operate on lines. Define support for line-based
    -- functions both within and without the IO monad.
    
    instance CommandLike ([String] -> IO [String]) where
        invoke func _ input =
               return $ CommandResult linedfunc (return (Exited ExitSuccess))
           where linedfunc = func (lines input) >>= (return . unlines)
    
    instance CommandLike ([String] -> [String]) where
        invoke func = invoke (unlines . func . lines)
    
    {- | Type representing a pipe. A 'PipeCommand' consists of a source
    and destination part, both of which must be instances of
    'CommandLike'. -}
    data (CommandLike src, CommandLike dest) => 
         PipeCommand src dest = PipeCommand src dest 
    
    {- | A convenient function for creating a 'PipeCommand'. -}
    (-|-) :: (CommandLike a, CommandLike b) => a -> b -> PipeCommand a b
    (-|-) = PipeCommand
    
    {- | Make 'PipeCommand' runnable as a command -}
    instance (CommandLike a, CommandLike b) =>
             CommandLike (PipeCommand a b) where
        invoke (PipeCommand src dest) closefds input =
            do res1 <- invoke src closefds input
               output1 <- cmdOutput res1
               res2 <- invoke dest closefds output1
               return $ CommandResult (cmdOutput res2) (getEC res1 res2)
    
    {- | Given two 'CommandResult' items, evaluate the exit codes for
    both and then return a "combined" exit code. This will be ExitSuccess
    if both exited successfully. Otherwise, it will reflect the first
    error encountered. -}
    getEC :: CommandResult -> CommandResult -> IO ProcessStatus 
    getEC src dest =
        do sec <- getExitStatus src
           dec <- getExitStatus dest
           case sec of
                Exited ExitSuccess -> return dec
                x -> return x
    
    {- | Different ways to get data from 'run'.
    * IO () runs, throws an exception on error, and sends stdout to stdout.
    * IO String runs, throws an exception on error, reads stdout into
    a buffer, and returns it as a string.
    * IO [String] is same as IO String, but returns the results as lines.
    * IO ProcessStatus runs and returns a ProcessStatus with the exit
    information. stdout is sent to stdout. Exceptions are not thrown.
    * IO (String, ProcessStatus) is like IO ProcessStatus, but also
    includes a description of the last command in the pipe to have
    an error (or the last command, if there was no error).
    * IO Int returns the exit code from a program directly. If a signal
    caused the command to be reaped, returns 128 + SIGNUM.
    * IO Bool returns True if the program exited normally (exit code 0,
    not stopped by a signal) and False otherwise.
    -}
    class RunResult a where
        {- | Runs a command (or pipe of commands), with results presented
           in any number of different ways. -}
        run :: (CommandLike b) => b -> a
    
    -- | Utility function for use by 'RunResult' instances
    setUpCommand :: CommandLike a => a -> IO CommandResult
    setUpCommand cmd = 
        do -- Initialize our closefds list
           closefds <- newMVar []
    
           -- Invoke the command
           invoke cmd closefds []
    
    instance RunResult (IO ()) where
        run cmd = run cmd >>= checkResult
    
    instance RunResult (IO ProcessStatus) where
        run cmd = 
            do res <- setUpCommand cmd
    
               -- Process its output
               output <- cmdOutput res
               putStr output
               getExitStatus res
    
    instance RunResult (IO Int) where
        run cmd = do rc <- run cmd
                     case rc of
                       Exited (ExitSuccess) -> return 0
                       Exited (ExitFailure x) -> return x
                       Terminated x -> return (128 + (fromIntegral x))
                       Stopped x -> return (128 + (fromIntegral x))
    
    instance RunResult (IO Bool) where
        run cmd = do rc <- run cmd
                     return ((rc::Int) == 0)
    
    instance RunResult (IO [String]) where
        run cmd = do r <- run cmd
                     return (lines r)
    
    instance RunResult (IO String) where
        run cmd =
            do res <- setUpCommand cmd
    
               output <- cmdOutput res
    
               -- Force output to be buffered
               evaluate (length output)
    
               ec <- getExitStatus res
               checkResult ec
               return output
    
    checkResult :: ProcessStatus -> IO ()
    checkResult ps =
        case ps of
             Exited (ExitSuccess) -> return ()
             x -> fail (show x)
    
    {- | A convenience function. Refers only to the version of 'run'
    that returns @IO ()@. This prevents you from having to cast to it
    all the time when you do not care about the result of 'run'.
    -}
    runIO :: CommandLike a => a -> IO ()
    runIO = run
    
    -------------------------------------------------------------- Utility Functions
    --------------------------------------------------------------
    cd :: FilePath -> IO ()
    cd = setCurrentDirectory
    
    {- | Takes a string and sends it on as standard output.
    The input to this function is never read. -}
    echo :: String -> String -> String
    echo inp _ = inp
    
    -- | Search for the regexp in the lines. Return those that match.
    grep :: String -> [String] -> [String]
    grep pat = filter (ismatch regex)
        where regex = mkRegex pat
              ismatch r inp = case matchRegex r inp of
                                Nothing -> False
                                Just _ -> True
    
    {- | Creates the given directory. A value of 0o755 for mode would be typical.
    An alias for System.Posix.Directory.createDirectory. -}
    mkdir :: FilePath -> FileMode -> IO ()
    mkdir = createDirectory
    
    {- | Remove duplicate lines from a file (like Unix uniq).
    Takes a String representing a file or output and plugs it through 
    lines and then nub to uniqify on a line basis. -}
    uniq :: String -> String
    uniq = unlines . nub . lines
    
    -- | Count number of lines. wc -l
    wcL, wcW :: [String] -> [String]
    wcL inp = [show (genericLength inp :: Integer)]
    
    -- | Count number of words in a file (like wc -w)
    wcW inp = [show ((genericLength $ words $ unlines inp) :: Integer)]
    
    sortLines :: [String] -> [String]
    sortLines = sort
    
    -- | Count the lines in the input
    countLines :: String -> IO String
    countLines = return . (++) "
    " . show . length . lines
    
    这里发生了什么变化:
    •A new CommandLikeinstance for  Stringthat uses the shell to evaluate and invoke
    the string.
    •New CommandLikeinstances for String -> IO Stringand various other types that
    are  implemented  in  terms  of  this  one.这些处理Haskell函数
    作为命令。
    •A new  RunResulttypeclass that defines a function  runthat returns information
    about the command in many different ways.更多信息见源中的注释。runIOis现在只是一个特定的RunResult实例的别名。
    •提供类似Unix shell命令的Haskell实现的几个实用函数。
    让我们尝试新的shell特性。首先,让我们确保我们在前面的例子中使用的命令仍然有效。然后,让我们使用一个更像shell的语法试试它。
    
    ghci> :load RunProcess.hs
    [1 of 1] Compiling RunProcess ( RunProcess.hs, interpreted )
    Ok, modules loaded: RunProcess.
    ghci> runIO $ ("ls", ["/etc"]) -|- ("grep", ["m.*ap"]) -|- ("tr", ["a-z", "A-Z"])
    Loading package array-0.1.0.0 ... linking ... done.
    Loading package bytestring-0.9.0.1.1 ... linking ... done.
    Loading package old-locale-1.0.0.0 ... linking ... done.
    Loading package old-time-1.0.0.0 ... linking ... done.
    Loading package filepath-1.1.0.0 ... linking ... done.
    Loading package directory-1.0.0.1 ... linking ... done.
    Loading package unix-2.3.0.1 ... linking ... done.
    Loading package process-1.0.0.1 ... linking ... done.
    Loading package regex-base-0.72.0.1 ... linking ... done.
    Loading package regex-posix-0.72.0.2 ... linking ... done.
    Loading package regex-compat-0.71.0.1 ... linking ... done.
    IDMAPD.CONF
    MAILCAP
    PM-UTILS-HD-APM-RESTORE.CONF
    ghci> runIO $ "ls /etc" -|- "grep 'm.*ap'" -|- "tr a-z A-Z"
    IDMAPD.CONF
    MAILCAP
    PM-UTILS-HD-APM-RESTORE.CONF
    
    这更容易输入。让我们试试替代我们的grep的原始的Haskell实现并尝试一些其他的新特性:
    
    ghci> runIO $ "ls /etc" -|- grep "m.*ap" -|- "tr a-z A-Z"
    IDMAPD.CONF
    MAILCAP
    PM-UTILS-HD-APM-RESTORE.CONF
    ghci> run $ "ls /etc" -|- grep "m.*ap" -|- "tr a-z A-Z" :: IO String
    "IDMAPD.CONF
    MAILCAP
    PM-UTILS-HD-APM-RESTORE.CONF
    "
    ghci> run $ "ls /etc" -|- grep "m.*ap" -|- "tr a-z A-Z" :: IO [String]
    ["IDMAPD.CONF","MAILCAP","PM-UTILS-HD-APM-RESTORE.CONF"]
    ghci> run $ "ls /nonexistant" :: IO String
    ls: cannot access /nonexistant: No such file or directory
    *** Exception: user error (Exited (ExitFailure 2))
    ghci> run $ "ls /nonexistant" :: IO ProcessStatus
    ls: cannot access /nonexistant: No such file or directory
    Exited (ExitFailure 2)
    ghci> run $ "ls /nonexistant" :: IO Int
    ls: cannot access /nonexistant: No such file or directory
    2
    ghci> runIO $ echo "Line1
    Hi, test
    " -|- "tr a-z A-Z" -|- sortLines
    HI, TEST
    LINE1
    
    关于管道的总结
    
    我们已经开发了一个复杂的系统。我们提醒你POSIX是复杂的。另一件事,我们需要强调的:你必须始终确保计算由这些函数返回的String,在你尝试计算子进程的退出代码之前。子进程往往不会退出,直到它写完所有的数据,如果你以错误的顺序这样做,你的程序将挂起。
    在本章中,我们从HSH的简版开始开发。如果您希望在自己的程序中使用这些类shell的能力,我们推荐HSH而不是这里开发的例子,由于HSH目前的优化。HSH也配备了大量的实用函数和更多的功能,但库后的源代码更复杂和庞大。这里介绍的一些实用函数,事实上,是逐字复制于HSH。HSH可用在http://software.complete.org/hsh。
    作者:Hevienz
    出处:http://www.cnblogs.com/hymenz/
    知识共享许可协议
    本博客原创作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
  • 相关阅读:
    利用MFC获取网页内容
    IP地址 >实际地址 ,API 查询
    一个小时内学习 SQLite 数据库
    Sqlite c/c++ api 学习
    笔记
    Sqlite的操作(增加,删除,查询,修改)
    免费天气API
    ServerSocketChannel的使用例子
    各种模式一览
    什么事文件描述符
  • 原文地址:https://www.cnblogs.com/hymenz/p/3345723.html
Copyright © 2011-2022 走看看