使用Python实现触摸功能?

509

touch 是一种Unix实用程序,它将文件的修改和访问时间设置为当天的当前时间。如果该文件不存在,则会使用默认权限创建。

您如何将其实现为Python函数?请尝试跨平台并完整。

(当前搜索“python touch file”的 Google 结果并不理想,但指向 os.utime。)


7
考虑到此功能现在已经集成到Python标准库中,建议您更新已接受的答案。 - Miles
@Miles 接受的答案完全符合问题的要求 - 它实际上在Python中实现了该函数,而不是使用库。 - styrofoam fly
10
标准库是 Python 的一部分。很可能该问题的提问者(以及大多数通过谷歌搜索进入这个问题的人)真正想知道的是如何在他们的 Python 程序中实现类似于 touch 功能,而不是如何从头开始重新实现它。对于那些人来说,最好向下滚动到 pathlib 解决方案。尽管现在内置了此功能,但是这个答案在“python touch file”的谷歌排名上比相关文档更高。 - Miles
1
@miles Python 2仍然比3更广泛地使用,所以我认为被接受的答案仍然更相关。但是你的评论很好地指引了人们去看第二个答案。 - itsadok
9
Python 2 将于今年年底停止更新。 - Max Gasner
16个回答

568

看起来这是Python 3.4以后的新功能 - pathlib

from pathlib import Path

Path('path/to/file.txt').touch()

这将在路径上创建一个file.txt文件。

--

Path.touch(mode=0o777, exist_ok=True)

在给定的路径上创建一个文件。如果提供了mode,则与进程的umask值结合使用以确定文件模式和访问标志。如果文件已经存在且exist_ok为true,则函数将成功(并将其修改时间更新为当前时间),否则会引发FileExistsError异常。


5
在Python2.7中:pip install pathlib - Andre Miras
21
自我提示:如果要创建文件的目录不存在,请使用 Path('/some/path').mkdir()。然后再执行 touch() 操作。 - JacobIRR
5
我认为我们应该使用pathlib2而不是pathlib,因为pathlib现在仅用于修复漏洞。因此,在Python 2.7上:请运行pip install pathlib2,然后导入Path时运行from pathlib2 import Path - Ian Lin
3
我很惊讶它默认为777而不是像644这样更保守的权限设置。 - zachaysan
2
我想指出 Path("...").touch() 不会返回任何值,所以如果你想在 .touch() 之前存储 Path("..."),就需要将其存储到一个变量中。 - Joe Sadoski
显示剩余4条评论

256

这个解决方案试图比其他解决方案更加避免竞争条件。(with关键词在Python 2.5中是新的。)

import os
def touch(fname, times=None):
    with open(fname, 'a'):
        os.utime(fname, times)

大致等同于这个。

import os
def touch(fname, times=None):
    fhandle = open(fname, 'a')
    try:
        os.utime(fname, times)
    finally:
        fhandle.close()

现在,为了确保它是无竞争的,您需要使用futimes并更改打开文件句柄的时间戳,而不是打开文件,然后更改文件名(可能已被重命名)的时间戳。不幸的是,Python似乎没有提供一种调用futimes而不经过ctypes或类似方式的方法...


编辑

Nate Parsons所指出的,Python 3.3将添加指定文件描述符(当os.supports_fd时),对诸如os.utime之类的函数进行操作,这将在底层使用futimes系统调用而不是utimes系统调用。换句话说:

import os
def touch(fname, mode=0o666, dir_fd=None, **kwargs):
    flags = os.O_CREAT | os.O_APPEND
    with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f:
        os.utime(f.fileno() if os.utime in os.supports_fd else fname,
            dir_fd=None if os.supports_fd else dir_fd, **kwargs)

FYI,似乎futimes是在3.3版本中添加的。 - Nate Parsons
注意:Python 3中已经移除了内置的“file”函数,必须使用“open”代替。我完全错过了这个问题,因为我使用的编辑器(gedit)的语法高亮仍然针对Python 2。 - Bart
@AurélienOoms:谢谢,当我最初写这个答案时,我只使用了文档(没有安装Python 3)。现在代码已经更新,可以正常工作了。 - ephemient
这似乎对目录无效(与Unix实用程序不同)- IOError:[Errno 21]是一个目录 - 如何修复? - jtlz2
你能解释一下它为什么比@jcoffland的解决方案更好吗? - Evpok
显示剩余8条评论

50
def touch(fname):
    if os.path.exists(fname):
        os.utime(fname, None)
    else:
        open(fname, 'a').close()

10
同意,正确的解决方案如下:def touch(fname): open(fname, 'wa').close() - stepancheg
@Greg,虽然它解决了潜在的竞争条件问题,但 open(fname, 'a').close() 不会改变 atime。 - SilentGhost
@SilentGhost:确实如此,但这没关系,因为如果文件存在,则它刚刚被创建。当然,对于已经存在的文件,您会将调用os.utime()保留在那里。 - Greg Hewgill
5
为什么不直接打开文件以确保它存在,然后调用utime函数? - itsadok
由于它会触发另一个竞态条件:如果文件在open()utime()调用之间被删除怎么办?在我看来,一个健壮的解决方案是在这里提出的。 - MestreLion

37

为什么不试试这个呢?:

import os

def touch(fname):
    try:
        os.utime(fname, None)
    except OSError:
        open(fname, 'a').close()

我相信这消除了任何重要的竞争条件。如果文件不存在,则将抛出异常。

此处唯一可能存在的竞争条件是,在调用open()之前但在os.utime()之后创建文件。但是,这并不重要,因为在这种情况下,修改时间将符合预期,因为它必须发生在touch()调用期间。


22
对于更底层的解决方案,可以使用
os.close(os.open("file.txt", os.O_CREAT))

作为对比,GNU touch使用了
O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY

使用MODE_RW_UGO模式,该模式为0o666

2
从文档中得知 os.O_CREAT − 如果文件不存在则创建文件 - Mitchell van Zuylen
1
如果文件已经存在,则修改时间不会被改变。 - undefined

19

自Python-2.5版本发布以来,本回答适用于所有版本,当时关键字with已经发布。

1. 如果不存在则创建文件 + 设置当前时间
(与touch命令完全相同)

import os

fname = 'directory/filename.txt'
with open(fname, 'a'):     # Create file if does not exist
    os.utime(fname, None)  # Set access/modified times to now
                           # May raise OSError if file does not exist

更健壮的版本:

import os

with open(fname, 'a'):
  try:                     # Whatever if file was already existing
    os.utime(fname, None)  # => Set current time anyway
  except OSError:
    pass  # File deleted between open() and os.utime() calls

2. 只需在文件不存在时创建该文件
(不更新时间)

with open(fname, 'a'):  # Create file if does not exist
    pass

3. 仅更新文件的访问/修改时间
(如果文件不存在,则不创建文件)

import os

try:
    os.utime(fname, None)  # Set access/modified times to now
except OSError:
    pass  # File does not exist (or no permission)

使用os.path.exists()并不能简化代码:

from __future__ import (absolute_import, division, print_function)
import os

if os.path.exists(fname):
  try:
    os.utime(fname, None)  # Set access/modified times to now
  except OSError:
    pass  # File deleted between exists() and utime() calls
          # (or no permission)

奖励:更新目录中所有文件的修改时间

from __future__ import (absolute_import, division, print_function)
import os

number_of_files = 0

#   Current directory which is "walked through"
#   |     Directories in root
#   |     |  Files in root       Working directory
#   |     |  |                     |
for root, _, filenames in os.walk('.'):
  for fname in filenames:
    pathname = os.path.join(root, fname)
    try:
      os.utime(pathname, None)  # Set access/modified times to now
      number_of_files += 1
    except OSError as why:
      print('Cannot change time of %r because %r', pathname, why)

print('Changed time of %i files', number_of_files)

8
这里有一些使用ctypes的代码(仅在Linux上进行过测试):

from ctypes import *
libc = CDLL("libc.so.6")

#  struct timespec {
#             time_t tv_sec;        /* seconds */
#             long   tv_nsec;       /* nanoseconds */
#         };
# int futimens(int fd, const struct timespec times[2]);

class c_timespec(Structure):
    _fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)]

class c_utimbuf(Structure):
    _fields_ = [('atime', c_timespec), ('mtime', c_timespec)]

utimens = CFUNCTYPE(c_int, c_char_p, POINTER(c_utimbuf))
futimens = CFUNCTYPE(c_int, c_char_p, POINTER(c_utimbuf)) 

# from /usr/include/i386-linux-gnu/bits/stat.h
UTIME_NOW  = ((1l << 30) - 1l)
UTIME_OMIT = ((1l << 30) - 2l)
now  = c_timespec(0,UTIME_NOW)
omit = c_timespec(0,UTIME_OMIT)

# wrappers
def update_atime(fileno):
        assert(isinstance(fileno, int))
        libc.futimens(fileno, byref(c_utimbuf(now, omit)))
def update_mtime(fileno):
        assert(isinstance(fileno, int))
        libc.futimens(fileno, byref(c_utimbuf(omit, now)))

# usage example:
#
# f = open("/tmp/test")
# update_mtime(f.fileno())

你有进行基准测试吗?我听说CPython和C之间的边界在使用ctypes时会变慢。例如,我听说pygame被修改为使用ctypes,但速度变得如此缓慢,以至于该项目被放弃了。 - dstromberg

6

简单易懂:

def touch(fname):
    open(fname, 'a').close()
    os.utime(fname, None)
  • open 确保文件存在
  • utime 确保时间戳更新

理论上,有可能在 open 后删除文件,导致 utime 抛出异常。但可以认为这是可以接受的,因为确实发生了一些不好的事情。


5
with open(file_name,'a') as f: 
    pass

1
“失败”:在使用Python 2.7.5和Red Hat 7(文件系统为XFS)时,with open(fn,'a'): pass或替代方案open(fn, 'a').close()不会更改修改时间。在我的平台上,这些解决方案只会在文件不存在时创建一个空文件。 :-/ - oHo

4
以下内容足够:
import os
def func(filename):
    if os.path.exists(filename):
        os.utime(filename)
    else:
        with open(filename,'a') as f:
            pass

如果您想为触摸设置特定的时间,请按照以下方式使用os.utime:
os.utime(filename,(atime,mtime))

在这里,atime和mtime都应该是int/float类型,并且应该等于您想要设置的时间的秒数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接