简体   繁体   中英

How can I change atime and mtime of a symbolic link from Python?

I have a Python 2.7 program which must create a symbolic link with a modification date in the past. I can create the link with os.symlink() , and os.utime() claims to set the access time and modification time of files, but when I use os.utime() on my newly-created symlink, it changes the atime and mtime of the file to which the symlink points, rather than the atime and mtime of the symbolic link.

What is the best way to set the access time and modification time of a symbolic link from Python code?

Here is a test program which demonstrates what I am doing:

#!/usr/bin/env python2.7
import os, datetime, time

if __name__ == '__main__':
    path1, path2 = 'source', 'link'
    if os.path.exists(path1):
        os.rmdir(path1)
    os.mkdir(path1)
    if os.path.lexists(path2):
        os.remove(path2)
    os.symlink(path1, 'link')

    lstat1, lstat2 = os.lstat(path1), os.lstat(path2)
    print("Before: {path1} atime {sa}, mtime {sm}, {path2} atime {la}, mtime {lm}".format(
        path1=path1, path2=path2, sa=lstat1.st_atime, sm=lstat1.st_mtime, 
        la=lstat2.st_atime, lm=lstat2.st_mtime))

    long_ago = datetime.datetime(datetime.date.today().year - 1,1,1,00,00,00)
    long_ago_posix = time.mktime(long_ago.timetuple())
    print("Desired: {path1} unchanged, {path2} atime {m}, mtime {m}".format(
        path1=path1, path2=path2, m=long_ago_posix))

    os.utime(path2, (long_ago_posix, long_ago_posix))

    lstat1, lstat2 = os.lstat(path1), os.lstat(path2)
    print("After: {path1} atime {sa}, mtime {sm}, {path2} atime {la}, mtime {lm}".format(
        path1=path1, path2=path2, sa=lstat1.st_atime, sm=lstat1.st_mtime, 
        la=lstat2.st_atime, lm=lstat2.st_mtime))

This is the misbehaviour I see. The "After:" times change for "source" and not for "link", but the reverse should happen:

% ../src/utime_symlink_test.py
Before: source atime 1514931280.0, mtime 1514931280.0, link atime 1514931280.0, mtime 1514931280.0
Desired: source unchanged, link atime 1483257600.0, mtime 1483257600.0
After: source atime 1483257600.0, mtime 1483257600.0, link atime 1514931280.0, mtime 1514931280.0
% ls -ldT source link
lrwxr-xr-x  1 myuser  staff   6  2 Jan 14:14:40 2018 link -> source
drwxr-xr-x  2 myuser  staff  68  1 Jan 00:00:00 2017 source

By contrast, touch -h changes the atime and mtime of the symlink as I want.

% touch -h -t 201701010000 link
% ls -ldT source link          
lrwxr-xr-x  1 myuser  staff   6  1 Jan 00:00:00 2017 link -> source
drwxr-xr-x  2 myuser  staff  68  1 Jan 00:00:00 2017 source

Maybe executing touch -h from Python is my best choice, but I'm hoping for something better.

升级到Python 3.6并使用follow_symlinks选项。

os.utime(path2, (long_ago_posix, long_ago_posix), follow_symlinks = False)

As @Barmar points out, Python 3's os.utime() has a parameter, follow_symlinks = False , which gives the behaviour the questioner wants. Unfortunately, Python 2's os.utime() does not permit this parameter.

An alternative for Python 2 is to call out to the touch command, using subprocess.call() . This actually works on Python 3 as well. However, I only tested it on Mac. It probably works on Linux, which has a similar touch utility pre-installed and a similar process invocation convention. It is not tested on Windows, and may well not work there unless you go out of your way to install a touch utility.

Here is the question's test program, rewritten to show these three options. Call it with a single argument, one of 2.utime (fails), 3.utime (succeeds, Python 3 only), or 2.touch (succeeds, maybe Mac or Linux only). Default is 2.utime .

import os, datetime, time, sys, subprocess

if __name__ == '__main__':
    method = 'missing' if len(sys.argv) < 2 else sys.argv[1]
    path1, path2 = 'source', 'link'
    if os.path.exists(path1):
        os.rmdir(path1)
    os.mkdir(path1)
    if os.path.lexists(path2):
        os.remove(path2)
    os.symlink(path1, 'link')

    lstat1, lstat2 = os.lstat(path1), os.lstat(path2)
    print("Before: {path1} atime {sa}, mtime {sm}, {path2} atime {la}, mtime {lm}".format(
        path1=path1, path2=path2, sa=lstat1.st_atime, sm=lstat1.st_mtime, 
        la=lstat2.st_atime, lm=lstat2.st_mtime))

    long_ago = datetime.datetime(datetime.date.today().year - 1,1,1,00,00,00)
    long_ago_posix = time.mktime(long_ago.timetuple())
    print("Desired: {path1} unchanged, {path2} atime {m}, mtime {m}".format(
        path1=path1, path2=path2, m=long_ago_posix))

    if method in ['missing', '2.utime']: 
        # runs on Python 2 or 3, always follows symbolic links
        os.utime(path2, (long_ago_posix, long_ago_posix))
    elif method in ['2.touch']:
        # runs on Python 2 or 3, tested on Mac only, maybe works on Linux, probably not Windows
        invocation = ['touch', '-h', '-t', long_ago.strftime('%Y%m%d%H%M.%S'), path2]
        subprocess.call(invocation)
    elif method in ['3.utime']:
        # runs on Python 3 only, changes links instead of following them
        os.utime(path2, (long_ago_posix, long_ago_posix), follow_symlinks=False)
    else:
        print("Don't recognise option {0}. Try 2.utime, 2.touch, or 3.utime .".format(method))

    lstat1, lstat2 = os.lstat(path1), os.lstat(path2)
    print("After: {path1} atime {sa}, mtime {sm}, {path2} atime {la}, mtime {lm}".format(
        path1=path1, path2=path2, sa=lstat1.st_atime, sm=lstat1.st_mtime, 
        la=lstat2.st_atime, lm=lstat2.st_mtime))

Here is the Python 3 os.utime() succeeding:

% python3 ../src/utime_symlink_test.py 3.utime
Before: source atime 1514961960.0, mtime 1514961960.0, link atime 1514961960.0, mtime 1514961960.0
Desired: source unchanged, link atime 1483257600.0, mtime 1483257600.0
After: source atime 1514961960.0, mtime 1514961960.0, link atime 1483257600.0, mtime 1483257600.0
% ls -ldT source link                                     
lrwxr-xr-x  1 myuser  staff   6  1 Jan 00:00:00 2017 link -> source
drwxr-xr-x  2 myuser  staff  68  2 Jan 22:46:00 2018 source

Here is the touch call succeeding on Python 2 (tested on Mac only):

% python ../src/utime_symlink_test.py 2.touch 
Before: source atime 1514961838.0, mtime 1514961838.0, link atime 1514961838.0, mtime 1514961838.0
Desired: source unchanged, link atime 1483257600.0, mtime 1483257600.0
After: source atime 1514961838.0, mtime 1514961838.0, link atime 1483257600.0, mtime 1483257600.0
% ls -ldT source link
lrwxr-xr-x  1 myuser  staff   6  1 Jan 00:00:00 2017 link -> source
drwxr-xr-x  2 myuser  staff  68  2 Jan 22:43:58 2018 source

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM