HaskellでFTPクライアント

in

コード片としてPerl/Net::FTPをよく利用していますが、前回に続きこの部分もNetwork.FTP.Clientを使ってHaskellで書いてみました。ダウンロード対象を記述した以下の形式のファイル

# 行頭# はコメント行
remote_file_name,local_file_name
:
:

を読み込んでサーバからダウンロードしリネームして保存するだけのサンプルです。

type RemoteFileName = FilePath
type LocalFileNmae = FilePath

splt :: String -> (RemoteFileName,LocalFileNmae)
splt xs = (r,l)
  where
   r = takeWhile (\x -> x /= ',') xs
   l = tail $ dropWhile(\x -> x /= ',') xs

dlst :: String -> IO [(RemoteFileName,LocalFileNmae)]
dlst dl = do cs <- readFile dl
             let fl = map splt $ rme $ lines cs
             return fl
                where
                 rme xs = filter(\x -> isCmnt x) $ filter(\x -> length x > 0) xs
                 isCmnt (x:_) = if x == '#' then False else True

で、[(リモートファイル名,ローカルファイル名)]のリストを作成し、後はリストを順にダウンロードします。

dld :: FTPConnection -> (RemoteFileName,LocalFileNmae) -> IO ()
dld c (r,l) = do
                  result <- getbinary c r
                  oh <- openBinaryFile l WriteMode
                  hPutStr oh (fst result)
                  hClose(oh)

main = do arg <- getArgs
          conn <- easyConnectFTP "ftp.server.com"
          login conn "account" (Just "password") Nothing
          cwd conn "path/to/file"
          l <- dlst (head arg)
          mapM (\x -> dld conn x) l
          quit conn

簡単に実装できましたが、perlのNet::FTPの

$ftp->get($remote,$local) or die "…";

のようにリネームしながらのダウンロードができないのは不便かな?getbinaryの結果を書き出したい名前で改めてwriteする必要がありました。ダウンロードしたファイルの中身を操作したいときは便利ですが、単に名前を変えてダウンロードしたいときはちょっと手間。リネームしないのならdownloadbinaryで一気にダウンロードできます。

後、getlinesもPrelude.lineと同様改行文字は取り除かれてしまうようなので行末処理は自前になります。インタプリタ上でIO ([String], FTPResult)を評価すると(改行も含んだ)全テキストが

Prelude Network.FTP.Client> getlines h "sample.pl"
(["#!/usr/bin/perl\n#\nuse strict;\n\nprint \"hello world\\n\";\n"],
  (226,["File send OK."]))

のように取得されれてしまうのでデバグにはずいぶんと悩みました。これが遅延評価というやつでしょうか?headとかtakeとかmapで行単位で評価すれば分割して取れるようです。(どうやって確認したらいいのかわかりませんが...)なので、

dld c (r,l) = do result <- getlines c r
                    oh <- openFile l WriteMode
                    hPutStr oh (concat $ map(\x -> x++['\n']) $ (fst rs))
                    hClose oh

こんな実装で行末変換しました。このあたりはちょっと実用に向かないかも....

この記事のトラックバックURL:

http://hippos-lab.com/blog/trackback/379

返信