我想在Haskell中重命名文件而不覆盖已存在的文件。如果目标文件存在,我想在我的代码中处理(通过在文件名中附加内容)。
来自System.Directory
的{{3}}说明:
renameFile old new
将现有文件系统对象的名称从old
更改为new
。如果new
对象已存在,则它将被old
对象原子替换。两条路径都不能引用现有目录。
是否有任何现有的模块或命令可以让我重命名而不会覆盖?
我知道我可以自己做检查。如果有经验的人写的功能,我会感觉很多。被覆盖的文件已经不复存在了。
更新
我想根据EXIF(类似于jhead
)的创建数据或标准化为照片拍摄时区的文件系统时间戳重命名照片,视频,实时照片。可能是两张照片是在完全相同的时间拍摄并最终使用相同的名称:2017-01-12 – 11-12-11.jpg
。 必须不会发生。第二张照片的名称应为2017-01-12 – 11-12-11a.jpg
。
答案 0 :(得分:2)
这是一个潜在的解决方案:
import System.Directory (doesFileExist, renameFile)
-- | Rename a src file as tgt file, safely. If the tgt file exists, don't
-- rename and return False. Otherwise, rename src to tgt and return True.
renameSafely :: FilePath -> FilePath -> IO Bool
renameSafely src tgt = do
exists <- doesFileExist tgt
if not exists
then (renameFile src tgt >> return True)
else return False
(免责声明:我没有通过GHC运行以确保它编译; then
子句中的“&gt;&gt;”可能是一个问题。)
如评论中所述,文件系统中存在潜在的竞争条件,其中两个进程尝试同时创建或重命名具有相同名称的文件。但是,正如您所指出的,这对您来说不太可能是一个问题。
如果renameSafely
返回IO False
,则只需尝试其他名称。 : - )
答案 1 :(得分:2)
POSIX能够创建新文件:原子地检查文件是否存在,只有通过O_EXCL
标志open()
才能创建文件。这使您可以在更明显的实现中避免竞争条件,在这种实现中,两个进程可以在它们创建文件之前检查文件是否存在,从而导致一个进程覆盖另一个进程。这可以在这里有所帮助:想法是在目标上专门创建一个空文件,然后仅在独占创建成功时才用重命名覆盖它。如果独占创建失败,则另一个进程已创建该文件。这在Haskell的unix
package中通过openFd
function公开,它可以成功,也可以抛出IOException
。它可以像这样使用:
module RenameNoOverwrite where
import Control.Exception
import Control.Monad
import Data.Bits
import System.Directory
import System.Posix.Files
import System.Posix.IO
renameFileNoOverwrite :: FilePath -> FilePath -> IO Bool
renameFileNoOverwrite old new = do
created <- handle handleIOException $ bracket createNewFile closeFd $ pure $ pure True
when created $ renameFile old new
return created
where
createNewFile = openFd new WriteOnly (Just defaultMode) defaultFileFlags {exclusive = True}
defaultMode = ownerReadMode .|. ownerWriteMode .|. groupReadMode .|. otherReadMode
handleIOException :: IOException -> IO Bool
handleIOException _ = return False
关键部分是{exclusive = True}
选项,可在O_EXCL
的结果调用中设置open()
标记。
Windows具有类似的功能,通过CREATE_NEW
标记显示到CreateFile
。还有一个MOVEFILE_REPLACE_EXISTING
<form className="commentForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Your name"
value={this.state.author}
onChange={this.handleAuthorChange}
/>
<input
type="text"
placeholder="Say something..."
value={this.state.text}
onChange={this.handleTextChange}
/>
<input type="submit" value="Post" />
</form>
旗帜看起来可能有用,但我从来没有使用它,文档也不是100%清楚的。这些是在Haskell的MoveFileEx
中公开的。
不幸的是,目前似乎并不是一种可移植的方法。