Shake工具:命令执行失败后自动删除目标文件的方法
确实,Shake本身并没有像make那样内置“命令执行失败时自动删除目标文件”的功能,但有几个简洁的技巧可以帮你解决这个问题,不用写繁琐的退出码检查或者手动处理临时文件的移动逻辑。
方案1:用actionCatch捕获异常并清理目标文件
你可以把整个MP3构建流程包裹在actionCatch里,一旦任何一步命令失败,就删除已经生成的目标文件,再重新抛出异常让Shake标记规则失败。这样代码逻辑很直观:
import Development.Shake import Development.Shake.FilePath import Control.Exception (throwM) safeBuildMP3 :: FilePath -> FilePath -> Action () safeBuildMP3 src dst = do let cleanup = removeFilesAfter dst ["//*"] -- 执行lame编码 cmd "lame" src dst -- 执行id3v2添加标签 cmd "id3v2" "--add-tag=ARTIST=YourArtist" dst -- 捕获任何异常,清理后重新抛出 `actionCatch` \e -> do cleanup throwM e -- 在你的Shake规则里使用 main :: IO () main = shakeArgs shakeOptions $ do want ["output.mp3"] "output.mp3" %> \out -> do let src = "input.wav" need [src] safeBuildMP3 src out
这个方法的好处是不需要改变你的构建路径逻辑,只是在失败时做针对性清理。
方案2:用临时文件先构建,成功后再移动(推荐)
虽然你提到不想用临时目录,但Shake的withTempFile工具函数已经帮你处理了临时文件的创建和自动清理,而且这个方法从根源上避免了残留文件的问题——只有当所有步骤都成功时,才会把最终文件移动到目标路径:
import Development.Shake import Development.Shake.FilePath main :: IO () main = shakeArgs shakeOptions $ do want ["output.mp3"] "output.mp3" %> \out -> do let src = "input.wav" need [src] -- 创建一个临时MP3文件,所有构建步骤完成后再移动到目标位置 withTempFile "mp3" $ \tmpPath -> do cmd "lame" src tmpPath cmd "id3v2" "--add-tag=ARTIST=YourArtist" tmpPath -- 只有前面的命令都成功,才会执行这一步移动 moveFile tmpPath out
如果lame或id3v2失败,临时文件会被Shake自动清理,目标路径不会生成任何文件,完全符合你的需求。而且这个模式可以很容易封装成通用函数,在其他类似场景复用。
补充说明
Shake的设计理念是给用户足够的灵活性来定制错误处理,所以没有像make那样的默认行为,但上面的两种方法都能简洁地实现你要的效果。个人更推荐第二种临时文件的方式,因为它的容错性更好,不会因为清理逻辑的疏漏留下残留文件。
内容的提问来源于stack exchange,提问作者user3416536




