Forum: PC-Programmierung via Batchfile- älteste Datei in Verzeichnis (mit Unterverzeichnissen) ermitteln


von Msp 4. (msp430_crew)


Lesenswert?

Hallöchen,

ich hänge etwas an einem kleinen Batch, der mir die älteste Datei 
bestimmt in einem Verzeichnis mit diversen Unterverezichnissen bestimmt.

Leider macht er das aber Unterverzeichnisweise. Sprich ich bekomme die 
älteste Datei des letzten Verzeichnisses :-(.

Gibt des hier überhaupt eine Lösung via Command Line?

Anbeid er Code:
1
rem älteste Datei bestimmen
2
@echo off
3
for /f %%a in ('dir %sicherung%\cleaner\temp\*.* /tc /s /a-d /b /od') do set oldest=%%a

: Bearbeitet durch User
von batchman (Gast)


Lesenswert?

Mit einer einzigen Zeile vermutlich nicht lösbar. Muss halt einzeln 
überprüft werden.
1
@echo off
2
setlocal EnableExtensions, EnableDelayedExpansion
3
4
rem initial values
5
set olddat=999999999999
6
set oldfile=""
7
8
rem loop through dirs
9
for /R %%A in (.) do (
10
  call :chkfile "%%A"
11
)
12
13
rem show final result and exit
14
echo !olddat! !oldfile!
15
goto :eof
16
17
:chkfile
18
rem find oldest file in act dir using dir command sorted by date (last write access)
19
rem and update lastdat, lastfile if older then previous result
20
21
pushd "%~1"
22
for /F "usebackq skip=5 tokens=1,2,4" %%B in (`dir /A:-D /N /O:D /T:W 2^>NUL`) do (
23
  rem change according to local date/time format
24
  rem 26.01.200011:33
25
  rem 012345678901234
26
  set actdat=%%B%%C
27
  set actdat=!actdat:~6,4!!actdat:~3,2!!actdat:~0,2!!actdat:~10,2!!actdat:~13,2!
28
  if "!actdat!" lss "!olddat!" (
29
    set olddat=!actdat!
30
    set oldfile="%%~fD"
31
  )
32
  rem exit after first file
33
  popd
34
  goto :eof
35
)
36
popd
37
goto :eof

von ah8 (Gast)


Lesenswert?

> Mit einer einzigen Zeile vermutlich nicht lösbar. Muss halt einzeln
> überprüft werden.

$ find . -type f -exec ls -l --time-style=+%Y%m%d {} \; | sort -k6,6 | 
awk '{print $7}' | head -1

find . geht rekursiv durch alle Unetrverzichnisse und liefert eine 
Liste aller Dateien
-type f beschränkt die Suche auf reguläre Dateien
-exec ls führt für jede gefundenen Datei das ls Kommando aus
-l gibt für jede Datei eine 'lange' Liste Eigenschaften aus
*--time-style=+%Y%m%d* setzt das Zeitformat auf YYYYMMDD
*sort -k6,6* sortiert die Liste nach Datum (der sechsten Spalte)
*awk '{print $7}'* druckt aus jeder Zeile die 7. Splte (den Dateinamen)
head -1 selektiert aus der liste die erste Zeile, welche genau die 
älteste Datei enthält

von batchman (Gast)


Lesenswert?

Da es explizit um ein Windows System geht, solltest du noch hinzufügen, 
woher der OP diese Unix-Ports beziehen kann, wie er sie zum Laufen 
bringt und welche spezifische Besonderheiten bei einem gawk, awk oder 
der Version aus den SFU zu beachten sind :)

von ah8 (Gast)


Lesenswert?

> Da es explizit um ein Windows System geht, solltest du noch hinzufügen,
> woher der OP diese Unix-Ports beziehen kann, wie er sie zum Laufen
> bringt und welche spezifische Besonderheiten bei einem gawk, awk oder
> der Version aus den SFU zu beachten sind :)

Nanu, habe ich da etwas überlesen? Das es Windows sein sollte habe ich 
nirgends gefunden. (OK, ich habe mich über die REMs gewundert, aber man 
kann ja nicht alle Skriptsprachen kennen ;-) Ich habe den kleinen 
Einzeiler aber auf einem Windows XP System getestet, und zwar unter 
Cygwin. Also auf http://www.cygwin.com/ die setup-x86.exe herunterladen, 
starten, Mirror auswählen, Basissystem installieren, falls eines der 
Kommandows fehlt: suchen und nachinstallieren, Terminal öffnen und los 
geht’s. Ich installiere Cygwin standardmäßig auf allen meinen Windows 
Systemen.

von ah8 (Gast)


Lesenswert?

Mir ist da noch eingefallen, dass es ja durchaus mehrere Dateien mit dem 
gleichen Zeitstempel geben kann, also muss man als Ergebnis nicht nur 
mit einer einzelnen Datei, sondern mit einer Menge rechnen. Die folgende 
Version wäre daher vielleicht besser:
1
$ find . -type f -exec ls -l --time-style=+%Y%m%d%H%M%S {} \; | sort -k6,6 | awk 'NR==1{time=$6; print $7} NR!=1{if($6==time) print $7}'

Außerdem ist das Zeitraster jetzt von einem Tag auf eine Sekunde 
verkürzt.

von Msp 4. (msp430_crew)


Lesenswert?

Kurz und knapp -> besten Dank :-)

von batchman (Gast)


Lesenswert?

Es wäre ja ein fast ein Grund zum Feiern, wenn ein Programm, und sei es 
noch so kurz, auf Anhieb fehlerfrei wäre.

Dateinamen mit Leerzeichen wurden nicht korrekt verarbeitet, da der 
default delimiter ja ebenfalls ein white space enthält. Betrifft auch 
die awk-Variante, so dass token=4 oder {print $7} den gleichen Mist 
ergeben.
1
:chkfile
2
rem find oldest file in directory passed by parameter %1
3
rem using dir command sorted by date (last write access)
4
rem and updates olddat, oldfile if older then previous result
5
6
pushd "%~1"
7
8
rem file names may contain blanks so token=4 must not be used directly
9
for /F "usebackq skip=5 tokens=1-3*" %%B in (`dir /A:-D /N /O:D /T:W 2^>NUL`) do (
10
11
  rem exit if result doesn't exist as file - caused by dir footer lines
12
  rem optional since comparing numerical timestamps using LSS should never fail
13
  rem (at least "999999999999" LSS "99999999999A" returns FALSE)
14
  if NOT EXIST "%%~E" goto :chkfile_return
15
16
  rem rearrange according to local date/time format
17
  rem 26.01.200011:33
18
  rem 012345678901234
19
  set actdat=%%B%%C
20
  set actdat=!actdat:~6,4!!actdat:~3,2!!actdat:~0,2!!actdat:~10,2!!actdat:~13,2!
21
  if "!actdat!" LSS "!olddat!" (
22
    set olddat=!actdat!
23
    set oldfile="%%~fE"
24
  )
25
  rem exit after first file
26
  goto :chkfile_return
27
)
28
29
:chkfile_return
30
popd
31
goto :eof

von ah8 (Gast)


Lesenswert?

batchman schrieb:
> Dateinamen mit Leerzeichen wurden nicht korrekt verarbeitet, da der
> default delimiter ja ebenfalls ein white space enthält. Betrifft auch
> die awk-Variante, so dass token=4 oder {print $7} den gleichen Mist
> ergeben.

Stimmt, solche Dateien habe ich nicht auf meine Rechnern, daher konnte 
ich die folgende Version auch nicht testen. Sollte aber gehen:
1
$ find . -type f -exec ls -l --time-style=+%Y%m%d%H%M%S {} \; | sort -k6,6 | sed 1d | awk 'NR==1{time=$6;print}NR!=1{if($6==time)print;else exit}' | sed -r 's/^([^ ]+[ ]){6}//'

Das awk-Skript ist jetzt auch ein bisschen effizienter.

von ah8 (Gast)


Lesenswert?

Sorry, so ist es richtig:
1
$ find . -type f -exec ls -l --time-style=+%Y%m%d%H%M%S {} \; | sort -k6,6 | awk 'NR==1{time=$6;print}NR!=1{if($6==time)print;else exit}' | sed -r 's/^([^ ]+[ ]){6}//'

Das sed 1d Kommando war nur zum testen drin, um die ersten Dateien mit 
gleichem Zeitstempel zu finden. Bei mir waren es die Dateien 2-5, also 
musste ich die erste los werden.

von batchman (Gast)


Lesenswert?

ah8 schrieb:
> Das awk-Skript ist jetzt auch ein bisschen effizienter.

Ich denke, der Flaschenhals ist nicht awk sondern die find/ls Kombi.

Wenn ich die Rekursion dem ls überlasse und die Auswertung mit awk 
nachbaue, ist es sogar etwas schneller als die cmd-Version. Auch mit dem 
hiesigen, steinalten cygwin - ls (GNU coreutils) 5.3.0, GNU Awk 3.1.5 
anno 2005.
1
F:\xxx>timeit -s find_oldest.cmd
2
200001261133 "F:\xxx\test Ordner\irg4p h50s.spi"
3
4
Elapsed Time:     0:00:01.151
5
Process Time:     0:00:00.180
6
7
8
$ time ls -lRQ --time-style=+%Y%m%d%H%M%S  | awk 'BEGIN{FS="\"";olddat=99999999999999}; /^\"\./{actdir=substr($2,2)}; /^-/{split($1,tmp," ");if (tmp[6]<olddat) {olddat=tmp[6];oldfile=actdir "/" $2}}; END{print olddat " \"" ENVIRON["PWD"] oldfile "\""}'
9
20000126103346 "/cygdrive/f/xxx/test Ordner/irg4p h50s.spi"
10
11
real    0m0.310s
12
user    0m0.120s
13
sys     0m0.200s
14
15
$ time find . -type f -exec ls -l --time-style=+%Y%m%d%H%M%S {} \; | sort -k6,6 | awk 'NR==1{time=$6;print}NR!=1{if($6==time)print;else exit}' | sed -r 's/^([^ ]+[ ]){6}//'
16
./test Ordner/irg4p h50s.spi
17
18
real    0m12.857s
19
user    0m19.400s
20
sys     0m7.881s

von ah8 (Gast)


Lesenswert?

batchman schrieb:

> Ich denke, der Flaschenhals ist nicht awk sondern die find/ls Kombi.

Der Meinung würde ich mich anschließen, insbesondere die -exec Option 
dürfte teuer sein. Sie startet für jede gefundenen Datei ein neues ls. 
Dafür muss sie eigens einen Prozess erzeugen und das Erzeugen von 
Prozessen ist für ein Betriebssystem verhältnismäßig aufwendig.

Allerdings mache ich mir meistens nicht die Mühe, solche Einzeiler zu 
optimieren. Oft sind sie einfach nur dafür da, mal schnell einen Job zu 
erledigen. Da ist es wichtiger, dass sie schnell geschrieben sind als 
dass sie schnell laufen. Die Rechenzeit ist schießlich billiger als 
meine :-) (Das zusätliche exit bot sich hier einfach nur an.)

von ah8 (Gast)


Lesenswert?

Wenn es auf Geschwindigkeit ankommt kann man natürlich auch gleich C 
nehmen. Das folgende Beispiel stammt im Wesentlich von K&R (Second 
Edition, 1988, Kapitel 8.6: Example – Listing Directories). Ich habe 
lediglich die dynamische Speicherverwaltung hinzugefügt und natürlich 
die Aktion für die gefundenen Dateien angepasst. Die Optionen sind als 
Beispiel für Modifikationen gedacht. Läuft auch unter cygwin (gcc 4.8.2) 
und sollte keine Probleme mit Leerzeichen machen :-)

1
#include <stdio.h>
2
#include <errno.h>
3
#include <error.h>
4
#include <stdlib.h>
5
#include <string.h>
6
#include <sys/types.h>
7
#include <sys/stat.h>
8
#include <unistd.h>
9
#include <dirent.h>
10
11
12
struct buffer
13
{
14
  char *start;
15
  char *end;
16
};
17
18
struct buffer *buffer_init(struct buffer *buffer, int n) {
19
  buffer->start = malloc(n);
20
  if ( buffer->start == NULL )
21
    error(-3, errno, "not enough memory");
22
  buffer->end = buffer->start+n;
23
  *buffer->start = '\0';
24
  return buffer;
25
}
26
27
void buffer_free(struct buffer *buffer) {
28
  free(buffer->start);
29
}
30
31
int buffer_append(struct buffer *buffer, char *string, int offset) {
32
  char *i = buffer->start + offset;
33
  char *end = buffer->end - 1;
34
  while ( *string != '\0' ) {
35
    if ( i == end ) {
36
      int oldsize = buffer->end - buffer->start;
37
      buffer->start = realloc(buffer->start, 2*oldsize);
38
      if ( buffer->start == NULL )
39
        error(-3, errno, "not enough memory");
40
      i = buffer->start + oldsize - 1;
41
      end = (buffer->end = buffer->start + 2*oldsize) - 1;
42
    }
43
    *i++ = *string++;
44
  }
45
  *i = '\0';
46
  return i - buffer->start;  
47
}
48
49
50
struct result
51
{
52
  struct buffer files;
53
  int index;
54
  unsigned long mtime;
55
  unsigned long atime;
56
  unsigned long ctime;
57
  unsigned long size;
58
};
59
60
61
int cmp_unsigned(unsigned long x, unsigned long y) { return x==y ? 0 : x<y ? -1 : 1; }
62
int min_mtime(struct stat *stbuf, struct result *result) { return cmp_unsigned(stbuf->st_mtime, result->mtime); }
63
int max_mtime(struct stat *stbuf, struct result *result) { return cmp_unsigned(result->mtime, stbuf->st_mtime); }
64
int min_atime(struct stat *stbuf, struct result *result) { return cmp_unsigned(stbuf->st_atime, result->atime); }
65
int max_atime(struct stat *stbuf, struct result *result) { return cmp_unsigned(result->atime, stbuf->st_atime); }
66
int min_ctime(struct stat *stbuf, struct result *result) { return cmp_unsigned(stbuf->st_ctime, result->ctime); }
67
int max_ctime(struct stat *stbuf, struct result *result) { return cmp_unsigned(result->ctime, stbuf->st_ctime); }
68
int min_size(struct stat *stbuf, struct result *result) { return cmp_unsigned(stbuf->st_size, result->size); }
69
int max_size(struct stat *stbuf, struct result *result) { return cmp_unsigned(result->size, stbuf->st_size); }
70
71
72
void findfile(struct buffer *name, int index, struct result *result, int (*cmp)(struct stat *, struct result *))
73
{
74
  struct stat stbuf;
75
  if ( stat(name->start, &stbuf) < 0 )
76
    error(-1, errno, "can't stat file '%s'", name->start);
77
  if ( (stbuf.st_mode & S_IFMT) == S_IFDIR ) {
78
    DIR *dir = opendir(name->start);
79
    struct dirent *direntry;
80
    if ( !dir )
81
      error(-2, errno, "can't open directory '%s'", name->start);
82
    while ( (direntry = readdir(dir)) != NULL ) {
83
      if ( strcmp(direntry->d_name, ".") == 0 )
84
        continue;
85
      if ( strcmp(direntry->d_name, "..") == 0 )
86
        continue;
87
      //printf("inode\t%8ld\t%s\n", direntry->d_ino, direntry->d_name);
88
      findfile(name, buffer_append(name, direntry->d_name, buffer_append(name, "/", index)), result, cmp);
89
    }
90
    closedir(dir);
91
  }
92
  else if ( (stbuf.st_mode & S_IFMT) == S_IFREG ) {
93
    //printf("mtime\t%8lu\t%8ld\t%s\n", result->mtime, stbuf.st_mtime, name->start);
94
    //printf("atime\t%8lu\t%8ld\t%s\n", result->atime, stbuf.st_atime, name->start);
95
    //printf("ctime\t%8lu\t%8ld\t%s\n", result->ctime, stbuf.st_ctime, name->start);
96
    //printf("size\t%8lu\t%8lu\t%s\n", result->size, stbuf.st_size, name->start);
97
    int comp = (*cmp)(&stbuf, result);
98
    if ( comp < 0 ) {
99
      result->mtime = (unsigned long) stbuf.st_mtime;
100
      result->atime = (unsigned long) stbuf.st_atime;
101
      result->ctime = (unsigned long) stbuf.st_ctime;
102
      result->size = (unsigned long) stbuf.st_size;
103
      result->index = buffer_append(&result->files, "\n", buffer_append(&result->files, name->start, 0));
104
    }
105
    else if ( comp == 0 )
106
      result->index = buffer_append(&result->files, "\n", buffer_append(&result->files, name->start, result->index));
107
108
  }
109
}
110
111
112
int main(int argc, char **argv)
113
{
114
  struct buffer name;
115
  struct result result;
116
  int (*cmp)(struct stat *, struct result *);
117
118
  buffer_init(&name, 256);
119
  buffer_init(&result.files, 1024);
120
  result.index = 0;
121
122
  if ( argc <= 1 )
123
    cmp = NULL;
124
  else if ( strcmp(argv[1], "-m") == 0 )
125
    cmp = min_mtime, result.mtime = (unsigned long) -1;
126
  else if (  strcmp(argv[1], "-M") == 0 )
127
    cmp = max_mtime, result.mtime = (unsigned long) 0;
128
  else if ( strcmp(argv[1], "-a") == 0 )
129
    cmp = min_atime, result.atime = (unsigned long) -1;
130
  else if (  strcmp(argv[1], "-A") == 0 )
131
    cmp = max_atime, result.atime = (unsigned long) 0;
132
  else if ( strcmp(argv[1], "-c") == 0 )
133
    cmp = min_ctime, result.ctime = (unsigned long) -1;
134
  else if (  strcmp(argv[1], "-C") == 0 )
135
    cmp = max_ctime, result.ctime = (unsigned long) 0;
136
  else if ( strcmp(argv[1], "-s") == 0 )
137
    cmp = min_size, result.size = (unsigned long) -1;
138
  else if (  strcmp(argv[1], "-S") == 0 )
139
    cmp = max_size, result.size = (unsigned long) 0;
140
  else
141
    cmp = NULL;
142
143
  if ( cmp == NULL )
144
    error(-4, 0, "usage: %s -[mMaAcCsS] <file(s)>\n"
145
      "-m  earliest modification time\n"
146
      "-M  latest modification time\n"
147
      "-a  earliest access time\n"
148
      "-A  latest access time\n"
149
      "-c  earliest creation time\n"
150
      "-C  latest Creation time\n"
151
      "-s  smallest size\n"
152
      "-S  largest size\n"
153
    , *argv);
154
  else
155
    --argc, ++argv;
156
157
  if ( argc == 1 )
158
    findfile(&name, buffer_append(&name, ".", 0), &result, cmp);
159
  else while ( --argc > 0 )
160
    findfile(&name, buffer_append(&name, *(++argv), 0), &result, cmp);
161
162
  fputs(result.files.start, stdout);
163
  buffer_free(&result.files);
164
  buffer_free(&name);
165
  return 0;
166
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.