Implémenter le toucher en utilisant Python?

352

touchest un utilitaire Unix qui définit les heures de modification et d'accès des fichiers à l'heure actuelle. Si le fichier n'existe pas, il est créé avec les autorisations par défaut.

Comment l'implémenteriez-vous en tant que fonction Python? Essayez d'être multiplateforme et complet.

(Les résultats actuels de Google pour "fichier tactile python" ne sont pas très bons, mais indiquent os.utime .)

itsadok
la source
4
Veuillez envisager de mettre à jour la réponse acceptée maintenant que cette fonctionnalité est intégrée dans le stdlib Python.
Miles
@Miles La réponse acceptée fait exactement ce que la question demandait - elle a en fait implémenté la fonction en Python au lieu d'utiliser une bibliothèque.
vol de mousse de polystyrène
5
@styrofoamfly La bibliothèque standard fait partie de Python. Il est très probable que ce que le demandeur de questions veut vraiment savoir (et la plupart des gens qui arrivent à cette question via Google) soit comment obtenir des touchfonctionnalités similaires à celles de leurs programmes Python, pas comment les réimplémenter à partir de zéro; ces personnes sont mieux servies en faisant défiler la liste jusqu'à la pathlibsolution. Même si elle est maintenant intégrée, cette réponse a un bien meilleur classement Google pour «fichier tactile python» que la documentation pertinente .
Miles
@miles Python 2 est (malheureusement) encore plus largement utilisé que 3, donc je pense que la réponse acceptée est toujours la plus pertinente. Mais votre commentaire fait du bon travail en pointant les gens vers la deuxième réponse.
itsadok
6
Python 2 est en fin de vie à la fin de cette année.
Max Gasner

Réponses:

304

On dirait que c'est nouveau depuis Python 3.4 - pathlib.

from pathlib import Path

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

Cela créera un file.txtau chemin.

-

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

Créez un fichier à ce chemin donné. Si le mode est donné, il est combiné avec la valeur umask du processus pour déterminer le mode de fichier et les drapeaux d'accès. Si le fichier existe déjà, la fonction réussit si exist_ok est vrai (et son heure de modification est mise à jour à l'heure actuelle), sinon FileExistsError est levée.

voidnologo
la source
3
Sur Python2.7:pip install pathlib
Andre Miras
8
note à soi: à utiliser Path('/some/path').mkdir()si le répertoire contenant le fichier à touch()éditer n'existe pas encore.
JacobIRR
1
Je pense que nous devrions utiliser à la pathlib2place de pathlibparce que le pathlibcorrectif de bogues est maintenant uniquement. Par conséquent, sur Python 2.7: pip install pathlib2et puis from pathlib2 import Path.
Ian Lin
@IanLin Il n'y a pas de raison d'installer une bibliothèque pour faire quelque chose que la bibliothèque standard prend déjà en charge. Confondez -vous bitbucket.org/pitrou/pathlib/src/default avec docs.python.org/dev/library/pathlib.html ?
Michael Mrozek
Ce commentaire répond au commentaire d'André parlant de Python 2.7, qui n'a pas cette bibliothèque standard. N'hésitez pas à lire le document sur pypi.org/project/pathlib2
Ian Lin
242

Cela essaie d'être un peu plus sans course que les autres solutions. (Le withmot-clé est nouveau dans Python 2.5.)

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

À peu près équivalent à cela.

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

Maintenant, pour le rendre vraiment sans course, vous devez utiliser futimeset modifier l'horodatage du descripteur de fichier ouvert, au lieu d'ouvrir le fichier, puis de modifier l'horodatage du nom de fichier (qui peut avoir été renommé). Malheureusement, Python ne semble pas fournir un moyen d'appeler futimessans passer par ctypesou similaire ...


ÉDITER

Comme l'a noté Nate Parsons , Python 3.3 ajoutera la spécification d'un descripteur de fichier (quand os.supports_fd) à des fonctions telles que os.utime, qui utiliseront le futimessyscall au lieu du utimessyscall sous le capot. En d'autres termes:

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)
éphémère
la source
C'est la vraie solution - et c'est ainsi que touch (1) dans coreutils le fait, à moins que futimes () ne soit pas disponible. futimes n'est pas une fonction portable et n'existe même pas sur les noyaux Linux 2.6 plus anciens, vous devez donc gérer ENOSYS et revenir à utime même si vous l'utilisez.
Glenn Maynard
(Erreur de relecture ci-dessus: "This" = open ("a") + futimes.) Heureusement, il est difficile de penser à un cas où la condition de concurrence de ne pas utiliser futimes importe réellement. Le "mauvais" cas dans lequel vous pourriez vous retrouver est le fichier renommé entre open () et utime (), auquel cas vous ne créerez pas de nouveau fichier ni ne toucherez l'ancien. Cela peut être important, mais la plupart du temps, ce ne sera pas le cas.
Glenn Maynard
cygwin touch peut faire sa magie sur les fichiers en lecture seule, mais pas ce code. Cependant cela semble fonctionner si je l'entoure avec try: <code> sauf IOError comme e: (check e.errno) os.utime (nom de fichier, heures)
dash-tom-bang
Pour info, il semble que futimes a été ajouté en 3.3
Nate Parsons
Remarque: la filefonction intégrée a été supprimée de Python 3 et opendoit être utilisée à la place. Cela m'a totalement manqué parce que la coloration syntaxique de l'éditeur que j'utilise (gedit) cible toujours Python 2.
Bart
42
def touch(fname):
    if os.path.exists(fname):
        os.utime(fname, None)
    else:
        open(fname, 'a').close()
SilentGhost
la source
24
Il existe une condition de concurrence potentielle dans cette solution: si le fichier n'existe pas et est créé par un autre processus avant que cette fonction n'atteigne l' open()appel, le contenu du fichier sera tronqué. Suggérez 'a'plutôt d' utiliser le mode .
Greg Hewgill
7
D'accord. La bonne solution est juste: def touch (fname): open (fname, 'wa').
Close
@Greg, même s'il résout le problème potentiel des conditions de course, open(fname, 'a').close()ne changera pas du tout.
SilentGhost
@SilentGhost: C'est vrai, mais ça va parce que si le fichier existe alors il vient d'être créé. Bien sûr, vous laisseriez l'appel à os.utime()des fichiers préexistants.
Greg Hewgill
4
Pourquoi ne pas simplement ouvrir pour vous assurer qu'il existe, puis appeler utime?
itsadok
31

Pourquoi ne pas essayer ça?:

import os

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

Je crois que cela élimine toute condition de course qui compte. Si le fichier n'existe pas, une exception sera levée.

La seule condition de concurrence possible ici est si le fichier est créé avant l'appel à open () mais après os.utime (). Mais cela n'a pas d'importance car dans ce cas le temps de modification sera comme prévu car il doit s'être produit lors de l'appel à toucher ().

jcoffland
la source
8

Voici du code qui utilise des ctypes (uniquement testé sur 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())
eug
la source
8

Cette réponse est compatible avec toutes les versions depuis Python-2.5 lorsque le mot clé witha été publié.

1. Créez un fichier s'il n'existe pas + Réglez l'heure actuelle
(exactement la même que la commande 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

Une version plus robuste:

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. Il suffit de créer le fichier s'il n'existe pas
(ne met pas à jour l'heure)

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

3. Il suffit de mettre à jour l'accès aux fichiers / les heures modifiées
(ne crée pas de fichier s'il n'existe pas)

import os

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

L'utilisation os.path.exists()ne simplifie pas le code:

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)

Bonus: heure de mise à jour de tous les fichiers d'un répertoire

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)
olibre
la source
4
with open(file_name,'a') as f: 
    pass
Matt
la source
Échec : with open(fn,'a'): passou une alternative open(fn, 'a').close()ne change pas l'heure modifiée en utilisant Python 2.7.5 sur Red Hat 7 (le système de fichiers est XFS). Sur ma plateforme, ces solutions créent simplement un fichier vide s'il n'existe pas. : - /
olibre
3

Simpliste:

def touch(fname):
    open(fname, 'a').close()
    os.utime(fname, None)
  • Le openassure qu'il y a un fichier
  • le utimegarantit que les horodatages sont mis à jour

Théoriquement, il est possible que quelqu'un supprime le fichier après le open, provoquant une exception pour utime. Mais c'est sans doute OK, car quelque chose de mauvais s'est produit.

itsadok
la source
1

Complexe (éventuellement buggy):

def utime(fname, atime=None, mtime=None)
    if type(atime) is tuple:
        atime, mtime = atime

    if atime is None or mtime is None:
        statinfo = os.stat(fname)
        if atime is None:
            atime = statinfo.st_atime
        if mtime is None:
            mtime = statinfo.st_mtime

    os.utime(fname, (atime, mtime))


def touch(fname, atime=None, mtime=None):
    if type(atime) is tuple:
        atime, mtime = atime

    open(fname, 'a').close()
    utime(fname, atime, mtime)

Cela essaie également de permettre de définir l'heure d'accès ou de modification, comme GNU touch.

itsadok
la source
1

Il peut sembler logique de créer une chaîne avec les variables souhaitées et de la transmettre à os.system:

touch = 'touch ' + dir + '/' + fileName
os.system(touch)

Ceci est inadéquat à plusieurs égards (par exemple, il ne gère pas les espaces blancs), alors ne le faites pas.

Une méthode plus robuste consiste à utiliser un sous-processus:

subprocess.call(['touch', os.path.join(dirname, fileName)])

Bien que ce soit beaucoup mieux que d'utiliser un sous-shell (avec os.system), il ne convient toujours qu'aux scripts rapides et sales; utilisez la réponse acceptée pour les programmes multiplateformes.

belacqua
la source
Ce n'est pas très sûr: que se passe-t-il quand il y a un espace dans le nom de fichier?
ayke
5
subprocess.call(['touch', os.path.join(dirname, fileName)])est beaucoup mieux que d'utiliser un sous-shell (avec os.system). Mais encore, n'utilisez cela que pour des scripts rapides et sales, utilisez la réponse acceptée pour les programmes multiplateformes.
ayke
1
touchn'est pas une commande multiplateforme (par exemple Windows)
Mike T
1

"open (nom_fichier, 'a'). close ()" ne fonctionnait pas pour moi dans Python 2.7 sous Windows. "os.utime (nom_fichier, aucun)" a très bien fonctionné.

De plus, j'avais besoin de toucher récursivement tous les fichiers d'un répertoire avec une date antérieure à une date. J'ai créé un suivi basé sur la réponse très utile de l'éphémient.

def touch(file_name):
    # Update the modified timestamp of a file to now.
    if not os.path.exists(file_name):
        return
    try:
        os.utime(file_name, None)
    except Exception:
        open(file_name, 'a').close()

def midas_touch(root_path, older_than=dt.now(), pattern='**', recursive=False):
    '''
    midas_touch updates the modified timestamp of a file or files in a 
                directory (folder)

    Arguements:
        root_path (str): file name or folder name of file-like object to touch
        older_than (datetime): only touch files with datetime older than this 
                   datetime
        pattern (str): filter files with this pattern (ignored if root_path is
                a single file)
        recursive (boolean): search sub-diretories (ignored if root_path is a 
                  single file)
    '''
    # if root_path NOT exist, exit
    if not os.path.exists(root_path):
        return
    # if root_path DOES exist, continue.
    else:
        # if root_path is a directory, touch all files in root_path
        if os.path.isdir(root_path):
            # get a directory list (list of files in directory)
            dir_list=find_files(root_path, pattern='**', recursive=False)
            # loop through list of files
            for f in dir_list:
                # if the file modified date is older thatn older_than, touch the file
                if dt.fromtimestamp(os.path.getmtime(f)) < older_than:
                    touch(f)
                    print "Touched ", f
        # if root_path is a file, touch the file
        else:
            # if the file modified date is older thatn older_than, touch the file
            if dt.fromtimestamp(os.path.getmtime(f)) < older_than:
                touch(root_path)
cadvena
la source
1

Pourquoi n'essayez-vous pas: newfile.py

#!/usr/bin/env python
import sys
inputfile = sys.argv[1]

with open(inputfile, 'w') as file:
    pass

python newfile.py foobar.txt

ou

utiliser un sous-processus:

import subprocess
subprocess.call(["touch", "barfoo.txt"])
suresh Palemoni
la source
0

Ce qui suit est suffisant:

import os
def func(filename):
    if os.path.exists(filename):
        os.utime(filename)
    else:
        with open(filename,'a') as f:
            pass

Si vous souhaitez définir une heure spécifique pour le toucher, utilisez os.utime comme suit:

os.utime(filename,(atime,mtime))

Ici, atime et mtime doivent tous deux être int / float et doivent être égaux au temps d'époque en secondes au temps que vous voulez régler.

Amar chand Dargad
la source
0

Si cela ne vous dérange pas, sauf ...

def touch_dir(folder_path):
    try:
        os.mkdir(folder_path)
    except FileExistsError:
        pass

Une chose à noter cependant, si un fichier existe avec le même nom, il ne fonctionnera pas et échouera en silence.

Homme des cavernes
la source
0

write_text()de pathlib.Pathpeut être utilisé.

>>> from pathlib import Path
>>> Path('aa.txt').write_text("")
0
SuperNova
la source