0

在尝试将一些(对我来说:)常见的 bash 习语转换为海龟脚本时,我仍然遇到问题。这是一篇很长的帖子,抱歉——你可以跳过介绍性的解释,直接跳到最后的实际问题——但我希望以这种方式清楚地表达我的观点(问题)。

我在 bash 脚本中经常使用的一个习惯用法是将 find、egrep 和 xargs 与空终止字符串链接在一起(管道)。原因很简单:即使是带有空格和其他奇怪字符的文件名也不会导致任何问题。

我会使用这样的东西:

寻找 。-name "*" -print0 ... | egrep -z -Z ... | xargs -0 ...

有时我想逐行处理与 -L 1 匹配的文件

寻找 。-name "*" -print0 ... | egrep -z -Z ... | xargs -0 -L 1 ...

或者,而不是 xargs -0... 我会使用另一个工具,比如 rsync with ssh,它也可以理解以空字符结尾的字符串:-0

将我当前目录的(基本)内容同步/保存到其他目录,例如。我会使用类似的东西:

binaries="exe$"
logfiles="log$"
pidfiles="pid$"
shakestuff="\_shake|\_build|\.\.database"
pat="^\.$|/dist|\.cabal-sandbox|cabal\.sandbox\.config|$shakestuff|\.o$|\.dyn_o$|\.hi$|\.dyn_hi$|\.hdevtools.sock$|$binaries|$logfiles|$pidfiles|TAGS"

find . -iname "*" -print0 -type f | egrep -z -Z -v "$pat" | rsync -a -e ssh --delete --progress --files-from=- -0 ./ .../path/to/some/other/dir
  • find 打印当前目录中的所有文件,以 null 结尾:-print0

  • egrep -v "$pat",仅保留与模式 $pat 不匹配的文件列表,仅保留基本文件,即:我不费心在 .cabal-sandbox 目录中同步/保存文件,例如,和 egrep处于此链的中间必须在此处消耗和生成以空值结尾的字符串: -z -Z 模式 pat 是预先逐个组装的。

  • 此处指示 rsync with ssh 从 stdin 获取输入:--files-from=-,再次以 null 终止:-0(请注意,虽然通常“rsync ... from to”的行为非常不同,具体取决于目录是否来自用斜杠给出,如这里:./ 或不是,这在这里不太重要,因为 rsync 的输入来自标准输入:-)

现在我试图把它变成一个乌龟脚本,取得了一些成功,但我仍然面临一些问题,并且想把它变成更惯用的乌龟:

为了完整起见,这是我当前在文件sync.hs中的工作脚本,在一个小的runturtle bash脚本的帮助下调用,我可以调用sync.hs

  1. 要么只显示正在考虑的文件列表:sync.hs -e

  2. 或将它们同步到另一个目录,如下所示:sync.hs --to /path/to/other/dir

这是代码(runturtle):

#!/bin/sh
exec cabal exec runhaskell -- "$@"

这是代码(sync.hs):

#!/usr/bin/env runturtle

{-# LANGUAGE OverloadedStrings #-}

-- {-# LANGUAGE ExtendedDefaultRules #-}

{-# OPTIONS_GHC -fno-warn-type-defaults #-}


import Turtle


data Opts = Opts {
  doEcho :: Bool
  , toDir :: Turtle.FilePath
  }
  deriving (Show)

parser :: Parser Opts
parser = Opts <$>
     (switch "echo" 'e' "echo the files considered for synchronizing")
     <*> (optPath "to" 't' "sync to dir")

binaries="|\\./website$|srv$"

logfiles="|log$"

pidfiles="|pid$|pnm$"

shakestuff="|_shake|_build|\\.\\.database"

pat="^\\.$"
  <>"|/dist|\\.cabal-sandbox|cabal\\.sandbox\\.config"
  <> shakestuff
  <>"|\\.git|\\.o$|\\.dyn_o$|\\.hi$|\\.dyn_hi$|\\.hdevtools.sock$"
  <> binaries
  <> logfiles
  <> pidfiles
  <>"|TAGS"

sync :: Opts -> IO ()
sync opts = do {

  ; echo "syncing..."
  ; when (doEcho opts)
(do {
    ; echo $ "pat: " <> pat

    ; sh $ do inproc  "find" [".", "-iname", "*", "-print0", "-type", "f"] empty 
        & inproc "egrep" ["-z", "-Z" , "-v", pat]
        & inproc "xargs" ["-0", "-L", "1"]
        & grep (has ".")
        >>= echo
    ; exit ExitSuccess
    })




  ; do {
  ; let txt = "find . -iname \"*\" -print0 -type f | egrep -z -Z -v \"" <> pat <>"\" | rsync -a -e ssh --delete  --progress --files-from=- -0 ./ "
          <> format fp (toDir opts)
  ; echo txt
  ; shell txt empty
  ; return ()
  }


  ; return ()

  }                                      



main :: IO ()
main = (do {
       ; opts <- options "sync file to another directory" parser
       ; print (opts)
       ; sync opts
       ; return ()

       })

现在这是我对这个脚本的问题:

首先:我可以在命令行上运行它,我在 emacs 中的 flycheck 语法检查依赖于 ghc 和其他 hdevtools 工作正常,因此现在可以从 Haskell 对 shell 脚本的强类型中受益(感谢顺便创建了 turtle )。我什至可以在命令行上使用 turtle (cabal repl)

cabal repl
> :set -XOverloadedStrings
> import Turtle
> ls "."
> view (shell "whatever cmd" empty)

等等,但是如果我加载我的 sync.hs 脚本,我无法访问它的片段(同步中定义的函数)

> :l sync.hs 
[1 of 1] Compiling Main             ( sync.hs, interpreted )
Ok, modules loaded: Main.

我想看看上面定义的模式,例如:

> pat

<interactive>:12:1:
Not in scope: ‘pat’
Perhaps you meant ‘cat’ (imported from Turtle)

我想使用 sync.hs 中定义的函数作为实验的快捷方式,例如。像这样

> view $ inproc "find" [".", "-iname", "*", "-print0", "-type", "f"] empty & inproc "egrep" ["-z", "-Z" , "-v", pat]

<interactive>:15:111:
Not in scope: ‘pat’
Perhaps you meant ‘cat’ (imported from Turtle)

其次,您可能已经注意到,在上面的 turtle 脚本中,我在 echo 的情况下使用了“更惯用的”turtle:

; sh $ do inproc  "find" [".", "-iname", "*", "-print0", "-type", "f"] empty 
    & inproc "egrep" ["-z", "-Z" , "-v", pat]
    & inproc "xargs" ["-0", "-L", "1"]
    & grep (has ".")
    >>= echo

IE。我正在使用海龟风格的管道:函数应用程序,这里与 & 的顺序相反,至少比 toDir 的情况更惯用,我实际上是依靠 bash 来完成这项工作:

; let txt = "find . -iname \"*\" -print0 -type f | egrep -z -Z -v \"" <> pat <>"\" | rsync -a -e ssh --delete  --progress --files-from=- -0 ./ "
    <> format fp (toDir opts)
; echo txt
; shell txt empty

但即使在这种更惯用的回声情况下,我也不得不求助于一些解决方法:grep(有“。”),如果我不使用它,我会看到空字符串:

turtle> view $ inproc "find" [".", "-iname", "*", "-print0", "-type", "f"] empty & inproc "egrep" ["-z", "-Z" , "-v", "\\.cabal-sandbox|/dist"]

输出(这里省略了很多输出,但在最后看到单个“\NUL”):

"...ntax.hs\NUL./static/lib-pi-forall/src/PiForall/Parser.hs\NUL./static/lib-pi-forall/src/PiForall/TypeCheck.hs\NUL./static/lib-pi-forall/LICENSE\NUL./shclean.do\NUL./TAGS\NUL./T10.hs\NUL./todo-yet-stop-the-program-as-in-running-if-not-told-another\NUL./talks\NUL./index.html\NUL./T1.hs.orig\NUL./sbbuild.sh\NUL./_shake\NUL./_shake/Main.hi\NUL./_shake/Main.dyn_o\NUL./_shake/build\NUL./_shake/Main.o\NUL./_shake/Main.dyn_hi\NUL./T4.hs\NUL./sync.hs\NUL./etc\NUL./.hdevtools.sock\NUL./more-stuff.hs\NUL./my.hs\NUL./T9.hs\NUL./snap-index\NUL./T6.hs\NUL./etc.html\NUL./cabalfile.hs\NUL./todo-maybe-issue-start-stop-restart-july2016\NUL./try-turtle-urwclassico.do\NUL./install.do\NUL./update-rc\NUL./index\NUL./done-pipe\NUL./clean.do\NUL./bootstrap.do\NUL./mystuff.cabal\NUL./pire\NUL./log\NUL./build.sh\NUL./goodsync.hs\NUL./cmds.hs\NUL./LICENSE\NUL./dry.do\NUL./T5.hs\NUL./snap-pire\NUL"
"\NUL"

如果我不费心用 grep 删除它们(有“。”),请查看最后得到的空字符串

turtle> view $ inproc "find" [".", "-iname", "*", "-print0", "-type", "f"] empty & inproc "egrep" ["-z", "-Z" , "-v", "\\.cabal-sandbox|/dist"] & inproc "xargs" ["-0", "-L", "1"]

(again lots of output omitted)
"./done-pipe"
"./clean.do"
"./bootstrap.do"
"./mystuff.cabal"
"./pire"
"./log"
"./build.sh"
"./goodsync.hs"
"./cmds.hs"
"./LICENSE"
"./dry.do"
"./T5.hs"
"./snap-pire"
""
""
""
""
turtle> 

为什么是这样?在 bash 中我不必这样做!在乌龟中使用空终止字符串的任何更好/推荐的方法?

最后同样重要的是,我无法为另一段 rsync 代码想出一个惯用的海龟解决方案。这是一个尝试,但看看会发生什么:一些文件被传输,但 rsync 抱怨我当前的目录 /home/rx/work/servant/ 未找到空终止:link_stat "/home/rx/work/servant/# 012”失败:(是的:它的名字只是“/home/rx/work/servant/”而不是“/home/rx/work/servant/#012”)

; view $ inproc "find" [".", "-iname", "*", "-print0", "-type", "f"] empty 
    & inproc "egrep" ["-z", "-Z", "-v", pat]
    & grep (has ".")
    & shell ("rsync -a -e ssh --delete --progress --files-from=- -0 ./ " <> (format fp $ toDir opts))


rx@softland ~/work/servant $ ./sync.hs --to ~/tmp/website_
Opts {doEcho = False, toDir = FilePath "/home/rx/tmp/website_"}
syncing...
building file list ... 
rsync: link_stat "/home/rx/work/servant/\#012" failed: No such file or directory (2)
135 files to consider
./
q
      8,715 100%    0.00kB/s    0:00:00 (xfr#1, to-chk=95/135)
sync.hs
      2,034 100%    1.94MB/s    0:00:00 (xfr#2, to-chk=86/135)
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1183) [sender=3.1.1]
ExitFailure 23
rx@softland ~/work/servant $

但实际上我什至想将 inproc 用于 rsync 片段(有或没有 grep(有“。”))

; view $ inproc "find" [".", "-iname", "*", "-print0", "-type", "f"] empty 
  & inproc "egrep" ["-z", "-Z", "-v", pat]
  & grep (has ".")
  & inproc "rsync" ["-a", "-e", "ssh", "--delete", "--progress", "--files-from=-", "-0", "./", format fp $ toDir opts]



rx@softland ~/work/servant $ ./sync.hs --to ~/tmp/website_
Opts {doEcho = False, toDir = FilePath "/home/rx/tmp/website_"}
syncing...
"building file list ... "
rsync: link_stat "/home/rx/work/servant/\#012" failed: No such file or directory (2)
" 0 files...\r 100 files...\r137 files to consider"
"./"
"sync.hs"
"\r          2,053 100%    0.00kB/s    0:00:00  \r          2,053 100%    0.00kB/s    0:00:00 (xfr#1, to-chk=86/137)"
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1183) [sender=3.1.1]
rx@softland ~/work/servant $

提前致谢。

4

0 回答 0