You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

如果lameid3v2失败,临时文件会被Shake自动清理,目标路径不会生成任何文件,完全符合你的需求。而且这个模式可以很容易封装成通用函数,在其他类似场景复用。

补充说明

Shake的设计理念是给用户足够的灵活性来定制错误处理,所以没有像make那样的默认行为,但上面的两种方法都能简洁地实现你要的效果。个人更推荐第二种临时文件的方式,因为它的容错性更好,不会因为清理逻辑的疏漏留下残留文件。

内容的提问来源于stack exchange,提问作者user3416536

火山引擎 最新活动