Changer la date de création/modification de fichiers avec un équivalent du "touch" unix/ms-dos

Soyez le premier à donner votre avis sur cette source.

Vue 10 273 fois - Téléchargée 796 fois

Description

Salut à tous !

Je poste ici le code source d'un petit utilitaire qui me permet de changer la date de création, de dernière modification ou de dernier accès d'un fichier, sous Windows.
Code écrit en C++/MFC, sous Visual C++ 6.0, pour toutes versions de Windows.
J'ai passé plus de temps à essayer de trouver un programme déjà tout fait ... que de l'écrire !
Coté source, j'ai vu celui de Nebula sur ce site, mais je cherchais un utilitaire un peu plus complet, qui soit à la fois simple et puissant (récursif), et puisse être appelé par un fichier "batch" (.bat), exactement comme le bon vieux touch.exe de MS-DOS... qui est devenu introuvable depuis !
Par rapport aux 2/3 utilitaires essayés, au moins, celui-ci est 100% gratuit, ne plante pas (!) et est donc meilleur pour la planète ;-))
Enfin, franchement, coté utilisation on perd moins de temps à taper la commande dans un shell DOS "à l'ancienne" que d'utiliser une interface fenêtrée et 36 clics à refaire à chaque fois...

Au fait, à quoi ça sert de changer la dates d'un fichier ?
C'est vrai, c'est assez rare... mais voici quelques exemples :
- remettre les photos numériques à la date de la prise de vue (en lisant les infos EXIF)
- taguer un ensemble de fichiers dans un répertoire
- corriger des problèmes de compilation (si la date du fichier projet est plus récente que celle des fichiers .c/.c++, il n'y a plus de compilation incrémentale)
- etc.

Source / Exemple :


// FTouch.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// The one and only application object

CWinApp theApp;

using namespace std;

CString	g_sFileFilter;
CTime	g_aTime=0;
CTime	g_mTime=0;
CTime	g_cTime=0;
BOOL	g_bRecurseDirs=FALSE;

// Output date format
const char g_szFmt[] = "%Y/%m/%d %H:%M:%S";

// Find a substring in a string without case sensitivity
LPCTSTR stristr( LPCTSTR string, LPCTSTR strCharSet )
{
	char *pStr=NULL;
	char szUpBuffer[1024]={0};
	char szUpCharSet[127]={0};
	UINT i;
	for (i=0;i<strlen(string    )+1;i++) szUpBuffer [i]=(char)toupper(string[i]);
	for (i=0;i<strlen(strCharSet)+1;i++) szUpCharSet[i]=(char)toupper(strCharSet[i]);
	pStr = strstr( szUpBuffer, szUpCharSet );
	if (!pStr) return NULL;
	i=pStr-szUpBuffer;
	pStr=(char *)string+i;
	return pStr;
}

// Function that tests 
BOOL FileMatch(LPCTSTR pszFileName, LPCTSTR pszFileFilter)
{
	int i0,i1,n0,n1,n;

	char szDrive[2][_MAX_DRIVE]={0};
	char szDir  [2][_MAX_DIR  ]={0};
	char szName [2][_MAX_FNAME]={0};
	char szExt  [2][_MAX_EXT  ]={0};
	char szPath [_MAX_PATH ]={0};
	LPCTSTR p=NULL;

	_splitpath(pszFileName  , szDrive[0], szDir[0], szName[0], szExt[0]);
	_splitpath(pszFileFilter, szDrive[1], szDir[1], szName[1], szExt[1]);
	// Remove the unnecessary point separator
	if (szExt[0][0]=='.') strcpy(&szExt[0][0],&szExt[0][1]);
	if (szExt[1][0]=='.') strcpy(&szExt[1][0],&szExt[1][1]);

	// Check if directories match
	i0=0; n0=strlen(szDir[0]);
	i1=0; n1=strlen(szDir[1]);
	n=max(n0,n1);
	p=stristr(szDir[0],szDir[1]);
	if (p) i0=p-szDir[0];
	if (n1>0) while (i0<n && i1<n)
	{
		char c0=toupper(szDir[0][i0]);
		char c1=toupper(szDir[1][i1]);
		switch (c1)
		{
			case '*' :	i0=n ; i1=n ;	break;
			case '?' :	i0++ ; i1++ ;	break;
			default  :	if (c0!=c1) return FALSE;
						i0++ ; i1++;	break;
		}
	}

	// Check if names match
	i0=0; n0=strlen(szName[0]);
	i1=0; n1=strlen(szName[1]);
	n=max(n0,n1);
	while (i0<n && i1<n)
	{
		char c0=toupper(szName[0][i0]);
		char c1=toupper(szName[1][i1]);
		switch (c1)
		{
			case '*' :	i0=n ; i1=n ;	break;
			case '?' :	i0++ ; i1++ ;	break;
			default  :	if (c0!=c1) return FALSE;
						i0++ ; i1++;	break;
		}
	}

	// Check if extensions match
	i0=0; n0=strlen(szExt[0]);
	i1=0; n1=strlen(szExt[1]);
	n=max(n0,n1);
	while (i0<n && i1<n)
	{
		char c0=toupper(szExt[0][i0]);
		char c1=toupper(szExt[1][i1]);
		switch (c1)
		{
			case '*' :	i0=n ; i1=n ;	break;
			case '?' :	i0++ ; i1++ ;	break;
			default  :	if (c0!=c1) return FALSE;
						i0++ ; i1++;	break;
		}
	}
	return TRUE;
}

// Base function that changes the date stamps of a file
BOOL Touch(LPCTSTR pszPath)
{
	CFileStatus status;
	CFile::GetStatus(pszPath,status);
	if (g_aTime>0) status.m_atime = g_aTime;
	if (g_cTime>0) status.m_ctime = g_cTime;
	if (g_mTime>0) status.m_mtime = g_mTime;
	CFile::SetStatus(pszPath,status);
	TRACE("c:%s m:%s a:%s \t%s\n",
		status.m_ctime.Format(g_szFmt),
		status.m_mtime.Format(g_szFmt),
		status.m_atime.Format(g_szFmt), pszPath);
	return TRUE;
}

// Recursive function that changes the date stamps of files
// matching the file filter. Returns the number of files touched
int TouchFiles(LPCTSTR pszPath,LPCTSTR pszFilter)
{
	int n=0;
	
	if (!SetCurrentDirectory(pszPath))
		return n;

	CFileFind FileFind;
	if ( !FileFind.FindFile("*.*") )
		return n;

	BOOL bNext;
	do
	{
		bNext = FileFind.FindNextFile();
		if (FileFind.IsDots()) continue;
		CString sPath = FileFind.GetFilePath();
		// On ajoute les fichier à la liste des fichiers
		if (FileFind.IsDirectory())
		{
			if (g_bRecurseDirs)
				n+= TouchFiles(sPath,pszFilter);
		}
		else
		{
			if (!FileMatch(sPath,pszFilter))
				continue;
			if (Touch(sPath)) n++;
		}
	} while (bNext);
//	TRACE("%s : %d datafiles found\n",pszPath,n);
	return n;
}

// Utility function that decodes times expressed in the command line
// If error, returns CTime(0) <=> Jan. 1st 1970, 01:00:00
CTime ExtractTime(LPCTSTR pszParam)
{
	CString s = pszParam;

	// Replace all exotic characters by whites
	s.Replace("/"," ");
	s.Replace(":"," ");
	s.Replace("_"," ");
	s.Replace("@"," ");
	s.Replace("  "," ");

	// Take current time as default
	CTime t=CTime::GetCurrentTime();
	int nYear = t.GetYear();
	int nMonth= t.GetMonth();
	int nDay  = t.GetDay();
	int nHour = t.GetHour();
	int nMin  = t.GetMinute();
	int nSec  = t.GetSecond();

	// Break-up the expression with whites
	int n = strlen(s);
	if (!strstr(s," ")) switch(n)
	{
		case 0 : break;
		case 6 :
			s.Insert( 4," ");
			s.Insert( 2," ");
			break;
		case 8 :
			s.Insert( 6," ");
			s.Insert( 4," ");
			break;
		case 12 :
			s.Insert(10," ");
			s.Insert( 8," ");
			s.Insert( 6," ");
			s.Insert( 4," ");
			s.Insert( 2," ");
			sscanf(s,"%d",&n);
			if (n>18 && n<21)	// Are the seconds or the century missing here ? Bug if (year%100)=19 or 20 !!!
				s = s.Left(2)+s.Right(14);
			break;
		case 14 :
			s.Insert(12," ");
			s.Insert(10," ");
			s.Insert( 8," ");
			s.Insert( 6," ");
			s.Insert( 4," ");
			break;
	}

	// Attribute years, months, days, hours, minutes, seconds
	n = strlen(s);
	switch(n)
	{
		case 0 : return CTime(nYear,nMonth,nDay,nHour,nMin,nSec);

		case 1 : return CTime(0);

		case 8 : 
		case 10: 
			n=sscanf(s,"%d %d %d",&nYear,&nMonth,&nDay);
			if (n!=3) return CTime(0);
			break;

		case 14 :
		case 16 :
			n=sscanf(s,"%d %d %d %d %d",&nYear,&nMonth,&nDay,&nHour,&nMin);
			if (n!=5) return CTime(0);
			nSec=0;
			break;

		case 17 :
		case 19 :
			n=sscanf(s,"%d %d %d %d %d %d",&nYear,&nMonth,&nDay,&nHour,&nMin,&nSec);
			if (n!=6) return CTime(0);
			break;

		default :
			return CTime(0);
	}

	// Check validity ranges
	if (nYear>=0 && nYear<1000 && nYear>50) nYear+=1900; else
	if (nYear>=0 && nYear<1000 && nYear<50) nYear+=2000;
	if (nYear < 0 || nYear >3000) return CTime(0);
	if (nMonth<=0 || nMonth>12) return CTime(0);
	if (nDay  <=1 || nDay  >31) return CTime(0);
	if (nHour < 0 || nHour >23) return CTime(0);
	if (nMin  < 0 || nMin  >59) return CTime(0);
	if (nSec  < 0 || nSec  >59) return CTime(0);
	
	// Build the time value to return
	t = CTime(nYear,nMonth,nDay,nHour,nMin,nSec);
	return t;
}

// Function that tests outputs (examples)
CString DateEx(LPCTSTR psz)
{
	CTime t = ExtractTime(psz);
	CString s=StringFormat("%-20s : %s",psz,t.Format(g_szFmt));
	return s;
}

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	int		nRetCode = 0;

	// initialize MFC and print and error on failure
	if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
	{
		// TODO: change error code to suit your needs
		cerr << _T("Fatal Error: MFC initialization failed") << endl;
		nRetCode = 1;
	}
	else
	{
		char drive[_MAX_DRIVE]={0};
		char dir  [_MAX_DIR  ]={0};
		char fname[_MAX_FNAME]={0};
		char ext  [_MAX_EXT  ]={0};
		char szBuffer[MAX_PATH]={0};

		// Handle command-line flags
		int nArgc = argc; 
		for (int i=argc-1;i>=1;i--)
		{
			LPCTSTR pszArg=argv[i];
			if ( strlen(pszArg)<2 ) continue;
			if ( pszArg[0]!='/' && pszArg[0]!='-' )
			{
				if ( g_sFileFilter=="")	g_sFileFilter = pszArg;
			}
			else
			{
				BOOL bFound=FALSE;
				if ( _strnicmp(&pszArg[1],"A=",2)==0) { g_aTime = ExtractTime(&pszArg[3]); if (g_aTime>0) TRACE("aTime=%s\n",g_aTime.Format(g_szFmt)); } else
				if ( _strnicmp(&pszArg[1],"M=",2)==0) { g_mTime = ExtractTime(&pszArg[3]); if (g_mTime>0) TRACE("mTime=%s\n",g_mTime.Format(g_szFmt)); } else
				if ( _strnicmp(&pszArg[1],"C=",2)==0) { g_cTime = ExtractTime(&pszArg[3]); if (g_cTime>0) TRACE("cTime=%s\n",g_cTime.Format(g_szFmt)); } else
				if ( _stricmp (&pszArg[1],"A"   )==0) { g_aTime = CTime::GetCurrentTime(); TRACE("aTime=%s\n",g_aTime.Format(g_szFmt)); } else
				if ( _stricmp (&pszArg[1],"M"   )==0) { g_mTime = CTime::GetCurrentTime(); TRACE("mTime=%s\n",g_mTime.Format(g_szFmt)); } else
				if ( _stricmp (&pszArg[1],"C"   )==0) { g_cTime = CTime::GetCurrentTime(); TRACE("cTime=%s\n",g_cTime.Format(g_szFmt)); } else
				if ( _stricmp (&pszArg[1],"S"   )==0) { g_bRecurseDirs = TRUE; } else
				if (bFound)
				{
					// Push command-line params
					for (int j=i;j<argc;j++) 
						argv[j] = (j<argc-1) ? argv[j+1] : NULL;
					argc--;
				}
				else
				{
					TRACE("%s : unknown flag !\n",pszArg);
				}
			}
		}

		if (g_sFileFilter!="")
		{
			char szDrive   [_MAX_DRIVE	]={0}; 
			char szDir	   [_MAX_DIR	]={0}; 
			char szName	   [_MAX_FNAME	]={0}; 
			char szExt	   [_MAX_EXT	]={0};
			char szInitFileDir[_MAX_PATH]={0};
			char szFileFilter [_MAX_PATH]={0};

			_splitpath(g_sFileFilter, szDrive, szDir, szName, szExt);
			_makepath (szInitFileDir, szDrive, szDir, NULL, NULL);
			_makepath (szFileFilter , NULL, NULL, szName, szExt);
			if (!szInitFileDir[0]) GetCurrentDirectory(_MAX_PATH,szInitFileDir);
			if (!szFileFilter[0]) strcpy(szFileFilter,"*.*");

			TouchFiles(szInitFileDir,szFileFilter);
		}
		else
		{
			TRACE("File touch utility\n");
			TRACE("\tUsage      : FTouch <filespec> /C /M /A /C=<date> /M=<date> /A=<date> \n");
			TRACE("\tDateFormats: %s\n",DateEx("20061129175900"));
			TRACE("\t             %s\n",DateEx("20061129"));
			TRACE("\t             %s\n",DateEx("061129"));
			TRACE("\t             %s\n",DateEx("2006_11_29"));
			TRACE("\t             %s\n",DateEx("2006_11_29_17_59_00"));
			TRACE("\t             %s\n",DateEx("2006/11/29_17:59"));
			TRACE("\t             %s\n",DateEx("06/11/29_17:59:00"));
		}
	}

#ifdef _DEBUG
	printf("\nPress a key to exit program...");
	getchar();
#endif

	return nRetCode;
}

Conclusion :


Voilà, c'est mon 1er post.
Je le publie juste pour aider les confrères qui ont les mêmes besoins que moi : retrouver l'équivalent du bon vieux "touch.exe"...

Comme vous le verrez, le code n'est pas révolutionnaire, mais simple et de bon-goût.
Aussi, je pense que ce code intéressera plutôt les débutants.
Désolé, les commentaires sont en anglais (contexte professionnel), mais je pense qu'en tant que développeurs, ça ne doit pas être un problème insurmontable...

Ah, dernière chose : je ne me souvenais plus de la syntaxe de la ligne de commande du programme original, alors j'en ai recréée une qui est simple et puissante.
Le programme lancé seul affiche cette syntaxe de ligne de commande.

Bonne lecture et au plaisir !

Codes Sources

A voir également

Ajouter un commentaire

Commentaires

BruNews
Messages postés
21042
Date d'inscription
jeudi 23 janvier 2003
Statut
Modérateur
Dernière intervention
21 août 2019
16
Salut,
Il n'y a que le vieux VS6 pour linker avec MFC42, toute version ultérieure doit fournir un setup ou livrer un exe monumental.

TESTONS:
#define _WIN32_WINNT 0x0600
#define _WIN32_IE 0x0700
#include <windows.h>

char szappname[] = "chrTst";

void fncTst(VOID)
{
char buf[40] = {0};
buf[0] = 'a';
MessageBox(0, buf, szappname, 0);
}

#pragma comment(linker, "/entry:myWinMain")
__declspec(naked) void __stdcall myWinMain()
{
__asm {
call fncTst
call fncTst
push 0
call dword ptr ExitProcess
}
}

LISTING ASM DU COMPILO:
_buf$ = -40
_fncTst@0 PROC
sub esp, 40
; 10 : char buf[40] = {0};
push 39
lea eax, DWORD PTR _buf$[esp+45]
push 0
push eax
call _memset
add esp, 12
; 11 : buf[0] = 'a';
; 12 : MessageBox(0, buf, szappname, 0);
push 0
push OFFSET _szappname
lea ecx, DWORD PTR _buf$[esp+48]
push ecx
push 0
mov BYTE PTR _buf$[esp+56], 97
call DWORD PTR __imp__MessageBoxA@16
add esp, 40
ret 0
_fncTst@0 ENDP

EXE fait 5 Ko et appel memset() sans équivoque.

Avec:
void fncTst(VOID)
{
char buf[40];
buf[0] = 'a';
buf[1] = 0;
MessageBox(0, buf, szappname, 0);
}

ON OBTIENT:
_buf$ = -40
_fncTst@0 PROC
sub esp, 40
; 9 : char buf[40];
; 10 : buf[0] = 'a';
; 11 : buf[1] = 0;
; 12 : MessageBox(0, buf, szappname, 0);
push 0
push OFFSET _szappname
lea eax, DWORD PTR _buf$[esp+48]
push eax
push 0
mov BYTE PTR _buf$[esp+56], 97
mov BYTE PTR _buf$[esp+57], 0
call DWORD PTR __imp__MessageBoxA@16
add esp, 40
ret 0
_fncTst@0 ENDP

EXE = 2.5 Ko et code optimal, y a pas photo.
Nicky22
Messages postés
4
Date d'inscription
vendredi 14 juillet 2006
Statut
Membre
Dernière intervention
19 janvier 2009

Re-salut BruNews, je n'avais pas vu votre pedigree... bravo !
Vous avez donc certainement de meilleures connaissances que moi en assembleur...
Du coup, j'ai eu un doute sur le nombre d'évaluations de strlen dans la boucle if.
Un petit bout de code de test m'a montré que vous aviez 100% raison.
Il vaut donc mieux remplacer mon code initial
for (i=0;i<strlen(sz);i++) { ... }
par
int n=strlen(sz);
for (i=0;i<n;i++) { ... }
A+
Nicky22
Messages postés
4
Date d'inscription
vendredi 14 juillet 2006
Statut
Membre
Dernière intervention
19 janvier 2009

Merci BruNews de votre commentaire.

Il est toujours possible de coder de multiples manières... et en fonction du but recherché (intérêt pédagogique, efficacité, robustesse, rapidité d'écriture, lisibilité, portabilité, ré-utilisabilité, etc.), certains choix sont meilleurs que d'autres.

Mais vous énoncez quelques vérités qui ... ne le sont pas :

- écrire char XX[xx]={0} n'équivaut pas à un memset, mais force juste le 1er caractère de la chaine à zéro. En debug, la chaine est de toute façon toute mise à zéro, mais pas en release. J'ai pris cette bonne habitude d'initialiser systématiquement chaque variable il y a longtemps...et ne m'en suis jamais plaint (sauf dans certains cas d'optimisation).
- le strlen dans le test de la boucle "if" ne sera évalué qu'une seule fois.
- Un setup pour déployer les MFC est inutile si on compile le projet en release, mfc42.dll se trouve dans Windows depuis 95/98 (et pas mfc42d.dll, si compilé debug, je vous l'accorde). Enfin, si ça vous inquiète toujours, vous pouvez toujours faire un "static link" avec les .lib des MFC...

Enfin, dans le rayon goûts et couleurs,
- j'aime pas les goto en C, mais à chacun ses marottes !
- OK pour la "surcouche" des MFC, on perd sans doute un peu de perfos (mais sont-elles vraiment requises dans un programme de ce genre ?). Avantages : facilité d'écriture, robustesse et lisibilité (qualités primordiales dans l'industrie). Inconvénient majeur : portabilité.

Pour illustrer ce dernier propos, seriez-vous prêt à réécrire la fonction CString::Replace() juste pour optimiser le code ? D'un point de vue pédagogique, c'est assez intéressant, mais bon...

En tous cas, merci de m'avoir donné l'occasion de débattre avec vous.
Intéressant !
Amicalement / NC
BruNews
Messages postés
21042
Date d'inscription
jeudi 23 janvier 2003
Statut
Modérateur
Dernière intervention
21 août 2019
16
Voyons cette fonction:
LPCTSTR stristr(LPCTSTR string, LPCTSTR strCharSet)
{
char *pStr=NULL;
char szUpBuffer[1024]={0};
char szUpCharSet[127]={0};
UINT i;
for(i 0; i < strlen(string) + 1; i++) szUpBuffer[i] (char)toupper(string[i]);
for(i 0; i < strlen(strCharSet) + 1; i++) szUpCharSet[i] (char)toupper(strCharSet[i]);
pStr = strstr(szUpBuffer, szUpCharSet);
if(!pStr) return NULL;
i = pStr - szUpBuffer;
pStr = (char*) string+i;
return pStr;
}

char szUpBuffer[1024]={0};
A quoi sert de forcer un memset() sur des buffers locaux ? à rien puisqu'on va y recopier des chaine qui ont donc un zero teminateur.

for(i 0; i < strlen(string) + 1; i++) szUpBuffer[i] (char)toupper(string[i]);
Recalcul strlen() à chaque tour ???
On remplacera par:
char *c, *d;
c = string;
d = szUpBuffer;
while(*c) *d++ = (char) toupper(*c++);
*d = 0;

On obtient donc:
LPCTSTR stristr(LPCTSTR string, LPCTSTR strCharSet)
{
char szUpBuffer[1024], szUpCharSet[127], *c, *d;
c = string;
d = szUpBuffer;
while(*c) *d++ = (char) toupper(*c++);
*d = 0;
c = strCharSet;
d = szUpCharSet;
while(*c) *d++ = (char) toupper(*c++);
*d = 0;
c = strstr(szUpBuffer, szUpCharSet);
if(!c) goto strEXIT;
c = string + (c - szUpBuffer);
strEXIT: return c;
}

Tout l'ensemble du code est à revoir sur ce modèle.

Autre sujet:
Pourquoi employer MFC (ou autre surcouche) pour un mini utilitaire de ce genre ?
Pour cause de MFC, il faut par force un setup pour le distribuer.
Il suffit de remplacer les CString et autres Ctrucmuche par un peu de code pour rendre l'exe indépendant.
gillardg
Messages postés
3275
Date d'inscription
jeudi 3 avril 2008
Statut
Membre
Dernière intervention
14 septembre 2014
3

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.