Discussion:
Zeitstempel mit touch aendern
(zu alt für eine Antwort)
Volker Englisch
2019-10-26 14:58:45 UTC
Permalink
Nach dem Übertragen größerer Mengen Bilder habe ich eine
Verzeichnisstruktur, die etwa so aussieht:

/zbv/Daten/Bilder/2017/Stuttgart/
/zbv/Daten/Bilder/2016/München/
etc.

Leider haben die Zeitstempel der Verzeichnisse das Datum des Kopierens.
Die einzelnen Bilddateien haben aber noch das originale Datum. Manuell
in der Shell eingegeben:

touch -r /zbv/Daten/Bilder/2017/Stuttgart/* /zbv/Daten/Bilder/2017/Stuttgart/

bewirkt genau das, was ich wollte, der Zeitstempel des Verzeichnisses
wird auf den Zeitstempel der letzten Datei in dem Verzeichnis gesetzt.
Manuell ist das allerdings sehr aufwändig, daher hatte ich folgendes
versucht:

find /zbv/Daten/Bilder -type d -exec touch -r "{}/*" "{}" \;

Ergibt leider für jedes Verzeichnis eine Fehlermeldung:

| touch: /zbv/Daten/Bilder/2017/Stuttgart/*: No such file or directory

Aber das Verzeichnis gibt es! Wo ist mein Denkfehler? Versucht habe ich
es mit einer Bourne-Shell, der ksh93 und der bash...

V.
Christian Weisgerber
2019-10-26 19:56:06 UTC
Permalink
Post by Volker Englisch
/zbv/Daten/Bilder/2017/Stuttgart/
/zbv/Daten/Bilder/2016/München/
Leider haben die Zeitstempel der Verzeichnisse das Datum des Kopierens.
Die einzelnen Bilddateien haben aber noch das originale Datum. Manuell
touch -r /zbv/Daten/Bilder/2017/Stuttgart/* /zbv/Daten/Bilder/2017/Stuttgart/
bewirkt genau das, was ich wollte, der Zeitstempel des Verzeichnisses
wird auf den Zeitstempel der letzten Datei in dem Verzeichnis gesetzt.
Nein.

Ich vereinfache die Pfade der Anschaulichkeit halber.
Wir haben ein Verzeichnis
Stuttgart/
mit den Dateien
Stuttgart/1
Stuttgart/2
Stuttgart/3

Bei

$ touch -r Stuttgart/* Stuttgart/

expandiert die Shell den Ausdruck mit dem '*' zu

$ touch -r Stuttgart/1 Stuttgart/2 Stuttgart/3 Stuttgart/

Das heißt, der Zeitstempel von Stuttgart/1 wird als Referenz genommen
und der von Stuttgart/2, Stuttgart/3 und Stuttgart/ dahin geändert.
Post by Volker Englisch
Manuell ist das allerdings sehr aufwändig, daher hatte ich folgendes
find /zbv/Daten/Bilder -type d -exec touch -r "{}/*" "{}" \;
| touch: /zbv/Daten/Bilder/2017/Stuttgart/*: No such file or directory
Der exec-Operator von find(1) ruft den Befehl, hier touch(1), direkt
auf ohne zwischengeschaltete Shell. Daher wird '*' nicht expandiert,
sondern wörtlich genommen. Und eine Datei mit dem Namen * existiert
im entsprechenden Verzeichnis eben nicht.
--
Christian "naddy" Weisgerber ***@mips.inka.de
Volker Englisch
2019-10-27 17:13:30 UTC
Permalink
Post by Christian Weisgerber
Post by Volker Englisch
/zbv/Daten/Bilder/2017/Stuttgart/
/zbv/Daten/Bilder/2016/München/
Leider haben die Zeitstempel der Verzeichnisse das Datum des Kopierens.
Die einzelnen Bilddateien haben aber noch das originale Datum. Manuell
touch -r /zbv/Daten/Bilder/2017/Stuttgart/* /zbv/Daten/Bilder/2017/Stuttgart/
bewirkt genau das, was ich wollte, der Zeitstempel des Verzeichnisses
wird auf den Zeitstempel der letzten Datei in dem Verzeichnis gesetzt.
$ touch -r Stuttgart/* Stuttgart/
expandiert die Shell den Ausdruck mit dem '*' zu
$ touch -r Stuttgart/1 Stuttgart/2 Stuttgart/3 Stuttgart/
Das heißt, der Zeitstempel von Stuttgart/1 wird als Referenz genommen
und der von Stuttgart/2, Stuttgart/3 und Stuttgart/ dahin geändert.
Die Erklärung klingt plausibel. Dennoch hatte das Verzeichnis Stuttgart
nach dem Befehl den Zeitstempel einer enthaltenen Datei aus 2017.
Post by Christian Weisgerber
Post by Volker Englisch
find /zbv/Daten/Bilder -type d -exec touch -r "{}/*" "{}" \;
| touch: /zbv/Daten/Bilder/2017/Stuttgart/*: No such file or directory
Der exec-Operator von find(1) ruft den Befehl, hier touch(1), direkt
auf ohne zwischengeschaltete Shell. Daher wird '*' nicht expandiert,
sondern wörtlich genommen. Und eine Datei mit dem Namen * existiert
im entsprechenden Verzeichnis eben nicht.
Ich war davon ausgegangen, dass die Shell alle Wildcards, die nicht in
'' eingeschlossen sind, als erstes expandiert. Aber selbst wenn es so
wäre, wäre Unsinn dabei herausgekommen. Das ist mir nur zu Anfang nicht
aufgefallen.

Danke für die ausführliche Erläuterung.
Helmut Waitzmann
2019-10-26 22:41:23 UTC
Permalink
Post by Volker Englisch
find /zbv/Daten/Bilder -type d -exec touch -r "{}/*" "{}" \;
| touch: /zbv/Daten/Bilder/2017/Stuttgart/*: No such file or directory
Aber das Verzeichnis gibt es! Wo ist mein Denkfehler? Versucht
habe ich es mit einer Bourne-Shell, der ksh93 und der bash...
Christian hat ja bereits alles nötige gesagt.


Hier noch ein Vorschlag für das „find“‐Kommando:


find /zbv/Daten/Bilder -type d \
-exec sh -c -- '
v="$1" && set "" "$v"/* &&
if ${2+:} false && test -e "$2"
then
shift $(( ${#} -1)) &&
touch -r "$1" -- "$v"
fi' sh "{}" \;
--
Hat man erst verstanden, wie Unix funktioniert, ist auch
das Shell-Handbuch kein Buch mit sieben Siegeln mehr.
Volker Englisch
2019-10-27 17:15:29 UTC
Permalink
Post by Helmut Waitzmann
Post by Volker Englisch
find /zbv/Daten/Bilder -type d -exec touch -r "{}/*" "{}" \;
find /zbv/Daten/Bilder -type d \
-exec sh -c -- '
v="$1" && set "" "$v"/* &&
if ${2+:} false && test -e "$2"
then
shift $(( ${#} -1)) &&
touch -r "$1" -- "$v"
fi' sh "{}" \;
Genial. Vielen herzlichen Dank! So tief war ich nie in die
Shell-Programmierung eingestiegen. Ich denke, ich verstehe ungefähr,
was da vorgeht. Nur das letzte "sh" in der Kette macht mir noch
Kopfzerbrechen ;-)
Ralf Damaschke
2019-10-27 18:38:56 UTC
Permalink
Post by Volker Englisch
Post by Helmut Waitzmann
find /zbv/Daten/Bilder -type d \
-exec sh -c -- '
v="$1" && set "" "$v"/* &&
if ${2+:} false && test -e "$2"
then
shift $(( ${#} -1)) &&
touch -r "$1" -- "$v"
fi' sh "{}" \;
Genial. Vielen herzlichen Dank! So tief war ich nie in die
Shell-Programmierung eingestiegen. Ich denke, ich verstehe ungefähr,
was da vorgeht. Nur das letzte "sh" in der Kette macht mir noch
Kopfzerbrechen ;-)
Du könntest es durch ein beliebiges Wort ersetzen und beobachten,
wie sich das auf ein zusätzliches 'echo "performing $0"' innerhalb
der inneren Kommandosequenz auswirkt.

Es ist aber ein unerwarteter Lapsus, auf Existenz der ersten
Datei der expandierten Liste zu prüfen, dann aber die letzte Datei
als Referenz zu nehmen. Zumal die 'shift'-Anweisung überflüssig ist,
es könnte weiterhin das alte '$2' referiert werden, sprich

'...
then
touch -r "$2" -- "$v"
fi' ...
Helmut Waitzmann
2019-10-28 13:26:03 UTC
Permalink
Post by Ralf Damaschke
Post by Helmut Waitzmann
find /zbv/Daten/Bilder -type d \
-exec sh -c -- '
v="$1" && set "" "$v"/* &&
if ${2+:} false && test -e "$2"
then
shift $(( ${#} -1)) &&
touch -r "$1" -- "$v"
fi' sh "{}" \;
Es ist aber ein unerwarteter Lapsus, auf Existenz der ersten
Datei der expandierten Liste zu prüfen, dann aber die letzte Datei
als Referenz zu nehmen.
Nein. Die letzte Datei als Referenz zu nehmen, war Vorgabe von
Volker. Das wollte ich nicht ändern.


Sicher ist es kein Fehler, auf die Existenz der Datei, deren Name im
letzten Element der expandierten Liste steht, zu prüfen. Nur,
wie will man das machen? Es gibt im Shell keine Möglichkeit,
„"${hier_bitte_die_Nummer_des_letzten_positional_parameters_verwenden}"“
anzugeben.


Aber der eigentliche Witz liegt woanders: Wenn der Shell ein
Dateinamensmuster in eine Liste von Dateinamen expandiert, gibt es
zwei Möglichkeiten:


1:


Der Shell findet eine oder mehrere Dateien, auf die das
Dateinamensmuster passt. Dann muss keiner der gefundenen
Dateinamen daraufhin überprüft werden, ob es die Datei gibt, denn
der Shell findet dann nur vorhandene Dateien.


2:


Der Shell findet keine Datei, die auf das Dateinamensmuster
passt. Dann erhält man nicht etwa keinen Dateinamen in der Liste
der gefundenen Dateinamen, sondern das Dateinamensmuster selbst
wird als Treffer zurückgegeben, obwohl es eine Datei dieses
Namens nicht gibt. Um dieses (unsinnige) Suchergebnis
abzufangen, ist der Vorhandenseinstest in der Kommandozeile
enthalten.
Post by Ralf Damaschke
Zumal die 'shift'-Anweisung überflüssig ist, es könnte weiterhin
das alte '$2' referiert werden, sprich
'...
then
touch -r "$2" -- "$v"
fi' ...
Die „shift“‐Anweisung ist nicht überflüssig, denn die Vorgabe von
Volker war, die letzte – nicht die erste – gefundene Datei als
Zeitstempelreferenz zu verwenden.


Allenfalls hätte ich die Reihenfolge der Aktionen umstellen
können: zuerst mit dem „shift“‐Kommando zum letzten
(möglicherweise nur angeblich) gefundenen Dateinamen vorzurücken
und danach den Vorhandenseinstest durchzuführen.


Ich wollte aber das Programmier‐Schema „nach dem Expandieren von
Dateinamensmustern im Shell nimmt man am besten das Abfangen des
oben mit „2:“ bezeichneten Sonderfalles sofort vor, damit man es
nicht später vergisst“ beibehalten.


Wäre ich nicht so vorgegangen, hätte in die Shell‐Kommandozeile
nach dem Set‐Kommando noch ein fettgedruckter Kommentar
hineingehört, der aussagt, dass die Liste der gefundenen
Dateinamen einen nur angeblich gefundenen Dateinamen enthalten
könnte.


(Schwieriger wird es, wenn man mehrere Dateinamensmuster angibt.
Dann könnte es ja durchaus sein, dass das erste Dateinamensmuster
einen oder mehrere Treffer findet, während das letzte mangels
Treffern unverändert stehen bleibt.)
Helmut Waitzmann
2019-10-28 15:03:41 UTC
Permalink
(Schwieriger wird es, wenn man mehrere Dateinamensmuster angibt. Dann
könnte es ja durchaus sein, dass das erste Dateinamensmuster einen
oder mehrere Treffer findet, während das letzte mangels Treffern
unverändert stehen bleibt.)
Dann könnte man so vorgehen:


set '' &&
for d in mehrere Dateinamensmuster ...
do
if test -e "$d"
then
set "$@" "$d"
fi
done &&
shift

Das geht aber auch nur dann gut, wenn jedes Dateinamensmuster auf
sich selbst passt (ansonsten gäbe es falsche Treffer).  Mit den
Dateinamensmustern im POSIX‐Standard („?“ und „*“) ist das aber
der Fall:  Ein Fragezeichen in einem Dateinamensmuster passt auf
ein Fragezeichen in einem Dateinamen; ein Stern in einem
Dateinamensmuster passt auf null oder mehr Sterne im Dateinamen.


Das Problem mit stehenbleibenden Dateinamensmustern kann man
beispielsweise im Bash mit der Shell‐Option „nullglob“ loswerden:


Das Kommando


„shopt -s nullglob“

bewirkt, dass Dateinamensmuster, für die keine Dateinamen
gefunden werden, nicht stehenbleiben, sondern entfernt werden. 
Damit wäre dann


set '' mehrere Dateinamensmuster ... &&
shift

sicher.
Christian Weisgerber
2019-10-28 17:02:43 UTC
Permalink
Post by Helmut Waitzmann
Es gibt im Shell keine Möglichkeit,
„"${hier_bitte_die_Nummer_des_letzten_positional_parameters_verwenden}"“
anzugeben.
eval \${$#}
--
Christian "naddy" Weisgerber ***@mips.inka.de
Helmut Waitzmann
2019-10-29 00:29:28 UTC
Permalink
Post by Christian Weisgerber
Post by Helmut Waitzmann
Es gibt im Shell keine Möglichkeit,
„"${hier_bitte_die_Nummer_des_letzten_positional_parameters_verwenden}"“
anzugeben.
eval \${$#}
Stimmt.  Innerhalb der Kommandozeile des von „find“ aufgerufenen
Shells wäre das dann beispielsweise


eval test -e \"\$\{"$#"\}\"

(O Graus!) gewesen.  Muss ja nicht unbedingt sein, wenn's auch
anders geht.

Ralf Damaschke
2019-10-28 23:37:18 UTC
Permalink
Post by Helmut Waitzmann
Post by Ralf Damaschke
Es ist aber ein unerwarteter Lapsus, auf Existenz der ersten
Datei der expandierten Liste zu prüfen, dann aber die letzte Datei
als Referenz zu nehmen.
Nein. Die letzte Datei als Referenz zu nehmen, war Vorgabe von
Volker. Das wollte ich nicht ändern.
OK, fiel mir nicht auf.

[...]
Post by Helmut Waitzmann
Ich wollte aber das Programmier‐Schema „nach dem Expandieren von
Dateinamensmustern im Shell nimmt man am besten das Abfangen des
oben mit „2:“ bezeichneten Sonderfalles sofort vor, damit man es
nicht später vergisst“ beibehalten.
So gesehen ist das natürlich in Ordnung, wenn auch zunächst
verblüffend. Ich dachte, es solle verhindert werden, dass touch
ins Leere greift (etwa bei kaputtem symbolischen Link).
Post by Helmut Waitzmann
Wäre ich nicht so vorgegangen, hätte in die Shell‐Kommandozeile
nach dem Set‐Kommando noch ein fettgedruckter Kommentar
hineingehört, der aussagt, dass die Liste der gefundenen
Dateinamen einen nur angeblich gefundenen Dateinamen enthalten
könnte.
Naja, einer in Normalschrift hätte es bei mir auch getan.
Helmut Waitzmann
2019-10-28 12:40:24 UTC
Permalink
Post by Volker Englisch
Post by Helmut Waitzmann
Post by Volker Englisch
find /zbv/Daten/Bilder -type d -exec touch -r "{}/*" "{}" \;
find /zbv/Daten/Bilder -type d \
-exec sh -c -- '
v="$1" && set "" "$v"/* &&
if ${2+:} false && test -e "$2"
then
shift $(( ${#} -1)) &&
touch -r "$1" -- "$v"
fi' sh "{}" \;
Genial. Vielen herzlichen Dank! So tief war ich nie in die
Shell-Programmierung eingestiegen. Ich denke, ich verstehe ungefähr,
was da vorgeht. Nur das letzte "sh" in der Kette macht mir noch
Kopfzerbrechen ;-)
Da wird ein Shell mit der Option „-c“ aufgerufen. Das
funktioniert so, wie in
<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html>
(oder auch im Handbuch des Shells) beschrieben (Zitate habe ich
jeweils mit „|“ am Zeilenanfang markiert):


Die drei grundlegenden Fälle, einen Shell zu starten, sind
folgende:

|
| SYNOPSIS
|
| sh [-abCefhimnuvx] [-o option]... [+abCefhimnuvx]
| [+o option]...
| [command_file [argument...]]
|
| sh -c [-abCefhimnuvx] [-o option]... [+abCefhimnuvx]
| [+o option]...
| command_string [command_name [argument...]]
|
| sh -s [-abCefhimnuvx] [-o option]... [+abCefhimnuvx]
| [+o option]...
| [argument...]

Hier interessiert der zweite Fall: mit der Option „-c“. (Der
erste Fall tritt auf, wenn man ein Shell‐Skript startet, der
dritte, wenn man einen Shell startet, der Kommandos von seiner
Standardeingabe entgegennimmt.)


|
| DESCRIPTION
|
| The sh utility is a command language interpreter that shall
| execute commands read from a command line string,
^^^^^^^^^^^^^^^^^^^^^^^^^^
(das nutze ich hier: mit der Option „-c“)


| the standard input, or a specified file.
[…]
|
| OPTIONS
|
[…]
|
| The following additional options shall be supported:
|
| -c
| Read commands from the command_string operand.
| Set the value of special parameter 0 (see Special
| Parameters) from the value of the command_name operand

Wenn man in der SYNOPSIS den Fall mit der Option „-c“ anschaut,
sieht man, dass nach dem „-c“ weitere Optionen kommen können.


Dann darf ein ausdrücklicher Optionenendeparameter, „--“, folgen,
wie es bei Optionen üblich ist. (Das wird an einer anderen Stelle
im Shell‐Handbuch, die ich jetzt nicht mitzitiert habe,
beschrieben.) So einen Parameter habe ich direkt nach der Option
„-c“ verwendet.


Als weitere Parameter in meinem Shell‐Aufruf folgt als erstes
einer, der sich über mehrere Zeilen erstreckt. Er ist das, was in
der SYNOPSIS mit „command_string“ bezeichnet wird: die dem Shell,
der von „find“ gestartet wird, übergebene Kommandozeile.


Als zweites folgt der Parameter „sh“. Er ist das, was in der
SYNOPSIS als „command_name“ bezeichnet wird. Der Shell verwendet
ihn beispielsweise bei Fehlermeldungen:


Probiere das folgende Kommando:


sh -c -- ': > /dev/null/Datei' 'Ich bin ein Shell'

Da versucht der Shell, die Datei „/dev/null/Datei“ zum Schreiben
zu öffnen. Das scheitert natürlich, weil „/dev/null“ kein
Verzeichnis sondern eine (Geräte‐)Datei ist. Deshalb erfolgt eine
Fehlermeldung.


Ich habe keine Ahnung, zu welchem Zweck die Shell‐Entwickler die
Übergabe eines frei wählbaren Namens damals so entworfen haben.
Man könnte es hier nutzen, indem man beispielsweise statt „sh“
„sh, von find aufgerufen“ angibt. Dann sieht man besser, wer
welche Fehlermeldungen spuckt.


Die zwei folgenden Parameter, „{}“ und „;“, werden von „find“
nicht an den Shell übergeben, sondern besonders behandelt: Statt
„{}“ übergibt „find“ dem Shell den Namen des jeweils gefundenen
Verzeichnisses; und „;“ verwendet „find“ selbst: Der „;“ zeigt
„find“ an, dass hier die Parameterliste des mit „-exec“
angegebenen Programmaufrufs zu Ende sein soll.
Volker Englisch
2019-10-28 21:12:52 UTC
Permalink
Post by Volker Englisch
Post by Helmut Waitzmann
Post by Volker Englisch
find /zbv/Daten/Bilder -type d -exec touch -r "{}/*" "{}" \;
find /zbv/Daten/Bilder -type d \
-exec sh -c -- '
v="$1" && set "" "$v"/* &&
if ${2+:} false && test -e "$2"
then
shift $(( ${#} -1)) &&
touch -r "$1" -- "$v"
fi' sh "{}" \;
Genial. Vielen herzlichen Dank! So tief war ich nie in die
Shell-Programmierung eingestiegen. Ich denke, ich verstehe ungefähr,
was da vorgeht. Nur das letzte "sh" in der Kette macht mir noch
Kopfzerbrechen ;-)
Als zweites folgt der Parameter ?sh?. Er ist das, was in der
SYNOPSIS als ?command_name? bezeichnet wird. Der Shell verwendet
Verstanden. Herzlichen Dank nochmal!
Loading...