Durchführen eines -nt / -ot-Tests in einem POSIX sh

11

Die integrierten testund [Dienstprogramme haben in den meisten Shells die Tests -nt("neuer als") und -ot("älter als"), auch wenn die Shell im "POSIX-Modus" ausgeführt wird (gilt auch für die gleichnamigen externen Dienstprogramme auf dem Systeme, auf die ich Zugriff habe). Diese Tests dienen zum Vergleichen von Änderungszeitstempeln für zwei Dateien. Ihre dokumentierte Semantik variiert geringfügig zwischen den Implementierungen (in Bezug darauf, was passiert, wenn die eine oder andere Datei nicht vorhanden ist), sie sind jedoch nicht in der POSIX-Spezifikation enthalten. für das testDienstprogramm .

Sie wurden nicht in das testDienstprogramm übernommen, als der bedingte Befehl aus der [KornShell] -Shell entfernt wurde, da sie nicht in das testDienstprogramm aufgenommen wurden, das in historischen Implementierungen des shDienstprogramms integriert ist.

Angenommen, ich möchte den Änderungszeitstempel zwischen Dateien in einem /bin/shShell-Skript vergleichen und dann Maßnahmen ergreifen, je nachdem, ob eine Datei neuer als die andere ist, wie in

if [ "$sigfile"     -nt "$timestamp" ] ||
   [ "$sigfile.tmp" -nt "$timestamp" ]
then
    return
fi

... welches andere Dienstprogramm könnte ich verwenden, abgesehen davon make(was den Rest des Skripts gelinde gesagt unhandlich machen würde)? Oder sollte ich einfach davon ausgehen, dass niemand das Skript jemals auf einer "historischen Implementierung von sh" ausführen oder sich damit abfinden wird, für eine bestimmte Shell wie zu schreiben bash?

Kusalananda
quelle
Wie Sie in meiner Antwort sehen können, implementiert bash die -ntFunktion von nicht testin einer Weise, wie es seit ca. 1995. Sie sollten den findbasd-Ausdruck auch dann verwenden, bashwenn Sie korrektes Verhalten mögen.
schily

Antworten:

10

POSIXLY:

f1=/path/to/file_1
f2=/path/to/file_2

if [ -n "$(find -L "$f1" -prune -newer "$f2")" ]; then
    printf '%s is newer than %s\n' "$f1" "$f2"
fi

Die Verwendung des absoluten Pfads zu Dateien verhindert, dass ein falsches Positiv mit Dateiname nur Zeilenumbrüche enthält.

Wenn Sie einen relativen Pfad verwenden, ändern Sie den findBefehl in:

find -L "$f1" -prune -newer "$f2" -exec echo . \;
cuonglm
quelle
technisch denke ich, dass es fehlschlägt, wenn es $f1nur Zeilenumbrüche enthält;)
ilkkachu
1
@ilkkachu könnte mit -exec echo x \;oder ähnlich gearbeitet werden?
Muru
@ Guru, ja. -printfwäre einfach, wenn es Standard wäre
ilkkachu
3
@Kusalananda AFAICT, findder Exit-Status ist unabhängig von den einzelnen findRückgabetests - außer wenn möglicherweise ein Fehler bei der tatsächlichen Ausführung dieser Tests vorliegt. findBeendet 0, auch wenn keine Dateien gefunden wurden.
Muru
1
Beachten Sie, dass das Verhalten von [ / -nt /nofile ]mit der Implementierung variiert (aber niemals einen Fehler ausgibt).
Stéphane Chazelas
7

Dies könnte ein Fall für die Verwendung eines der ältesten Unix-Befehle sein ls.

x=$(ls -tdL -- "$a" "$b")
[ "$x" = "$a
$b" ]

Das Ergebnis ist wahr, wenn a neuer als b ist.

meuh
quelle
3
Das funktioniert nicht, wenn Dateinamen mit -(fehlen --) beginnen. Sie müssten -Les gleichwertig sein mit -nt. Das funktioniert nicht zum Vergleichen xund $'x\nx'zum Beispiel.
Stéphane Chazelas
1
Eek! Aber auch huh.
Kusalananda
4

Sie haben eine interessante Frage aufgeworfen und eine Behauptung aufgestellt, die zuerst überprüft werden sollte.

Ich habe das Verhalten überprüft von:

$shell -c '[ Makefile -nt SCCS/s.Makefile ] && echo newer'

mit verschiedenen Muscheln. Hier sind die Ergebnisse:

  • Bash funktioniert nicht - druckt nichts.

  • bosh funktioniert

  • Bindestrich funktioniert nicht - druckt nichts.

  • ksh88 Funktioniert nicht - druckt nichts.

  • ksh93 funktioniert

  • mksh Funktioniert nicht - druckt nichts.

  • posh druckt: posh: [: -nt: unerwarteter Operator / Operand

  • Yash funktioniert

  • zsh funktioniert in neueren Versionen , ältere Versionen drucken nichts

Vier von neun Shells unterstützen die Funktion -nt und implementieren sie korrekt. Richtig bedeutet in diesem Fall: Zeitstempel auf aktuellen Plattformen vergleichen können, die eine Zeitstempelgranularität von weniger als einer Sekunde unterstützen . Beachten Sie, dass sich die von mir ausgewählten Dateien in ihren Zeitstempeln normalerweise nur wenige Mikrosekunden unterscheiden.

Da es einfacher ist, eine funktionierende findImplementierung zu finden , empfehle ich, diese zu ersetzen

if [ "$file1" -nt "$file2" ] ; then
    echo newer
fi

durch einen findbasierten Ausdruck.

if [ "$( find "$file1" -newer "$file2" )" ]; then
    echo newer
fi

funktioniert mindestens so lange, wie $file1nicht nur Zeilenumbrüche enthalten.

if [ "$( find -L "$file1" -newer "$file2" -exec echo newer \; )" ]; then
    echo newer
fi

ist etwas langsamer, funktioniert aber richtig.

Übrigens: In Bezug auf make kann ich nicht für alle make-Implementierungen sprechen, SunPro Makeunterstütze aber den Zeitvergleich mit der Nanosekunden-Granularität, da ca. 20 Jahre, während smakeund gmakediese Funktion vor kurzem hinzugefügt.

schily
quelle
1
Funktionieren die "nicht funktionierenden" Tests nicht, weil Zeitstempel im Subsekundenbereich (definitiv?) Nicht verglichen werden können, oder etwas anderes? Was sind die tatsächlichen Zeitstempel für die an Ihrem Test beteiligten Dateien? +1 für den Test!
Kusalananda
2
Ich denke, bei der Abwertung geht es wahrscheinlich um die konfrontative Formulierung. Hier wäre es fairer, es als Einschränkung zu bezeichnen (in einigen Zusammenhängen von Bedeutung). Einige findImplementierungen wie Busybox oder Heirloom-Toolchest haben die gleiche Einschränkung.
Stéphane Chazelas
3
-LDie findVersion muss der Version entsprechen -nt. Es würde auch die Dateinamen nicht auf , die mit Start -oder !, (...
Stéphane Chazelas
2
Tatsächlich bekomme ich oft Abstimmungen, wenn ich eine konfrontative Formulierung verwende. Hier wäre es hilfreich, wenn Sie den Kontext (Dateien, die innerhalb desselben Zeitstempels geändert wurden, wenn sie auf die zweite Auflösung gekürzt wurden) zu Beginn der Antwort angegeben hätten.
Stéphane Chazelas
1
@schily, ja, BSDs findhaben find -f "$file"dafür, aber es ist nicht portabel.
Stéphane Chazelas