Vergleichen Sie zwei Datumsänderungsdaten

8

Ich erstelle ein generisches Kompilierungs- / Transpilationssystem. Eine Möglichkeit, festzustellen, ob eine Datei bereits kompiliert / transpiliert wurde, besteht darin, die Änderungsdaten der Quell- und Zieldatei zu vergleichen.

Ich muss ein Bash-Skript schreiben, das das kann:

source_file=foo;
target_file=bar;

stat_source=$(stat source_file);
stat_target=$(stat target_file);

Aber wie kann ich die Daten aus der Statistikausgabe extrahieren und vergleichen? Gibt es eine bessere Möglichkeit, als statdie letzte Änderungszeit einer Datei zu vergleichen?

Wenn ich stat in einer Protokolldatei aufrufe, erhalte ich Folgendes:

16777220 12391188 -rw-r--r-- 1 alexamil staff 0 321 "Jun 22 17:45:53 2017" "Jun 22 17:20:51 2017" "Jun 22 17:20:51 2017" "Jun 22 15:40:19 2017" 4096 8 0 test.log

AFAICT, die Zeitgranularität ist nicht feiner als Sekunden. Ich muss etwas detaillierteres bekommen, wenn es möglich ist.

Alexander Mills
quelle

Antworten:

17

Da Sie die stat(ähnliche Funktionalität, aber unterschiedliches Ausgabeformat für BSDs und GNU) verwenden, können Sie auch das testDienstprogramm verwenden, das diesen Vergleich direkt durchführt:

   FILE1 -nt FILE2
          FILE1 is newer (modification date) than FILE2

   FILE1 -ot FILE2
          FILE1 is older than FILE2

In Ihrem Beispiel

if [ "$source_file" -nt "$target_file" ]
then
    printf '%s\n' "$source_file is newer than $target_file"
fi

Die Funktion ist in POSIX nicht verfügbar (siehe Dokumentation zutest ). Dies enthält folgende Gründe:

Einige zusätzliche Vorwahlen, die neu erfunden wurden oder aus der KornShell stammen, wurden in einem frühen Vorschlag als Teil des bedingten Befehls ([[]]) veröffentlicht: s1 >s2, s1 <s2, str = Muster, str! = Muster, f1 -ntf2, f1 -otf2 und f1 -ef f2. Sie wurden nicht in das Testdienstprogramm übertragen, als der bedingte Befehl aus der Shell entfernt wurde, da sie nicht in das Testdienstprogramm aufgenommen wurden, das in historischen Implementierungen des Dienstprogramms sh integriert ist.

Dies könnte sich jedoch in Zukunft ändern, da die Funktion weitgehend unterstützt wird.

Beachten Sie, dass bei Operanden als Symlinks die Änderungszeit des Ziels des Symlinks berücksichtigt wird (was im Allgemeinen gewünscht wird, verwenden Sie find -newerstattdessen, wenn nicht). Wenn Symlinks nicht aufgelöst werden können, das Verhalten zwischen Implementierungen (einige betrachten eine vorhandene Datei als immer neuer als eine, die nicht aufgelöst werden kann, andere melden immer false, wenn einer der Operanden nicht aufgelöst werden kann).

Beachten Sie auch , dass nicht alle Implementierungen unterstützen Unter zweiter Granularität ( bashs test/ [builtin ab Version 4.4 noch nicht zum Beispiel GNU testund die testbuiltin von zshoder zu ksh93tun, zumindest auf GNU / Linux).

Als Referenz:

  • Für die GNU testUtility - Implementierung (obwohl beachten Sie, dass Ihre Shell, wenn fishoder Bourne-like, wird auch eine haben test/ [builtin , dass es in der Regel Schatten, verwenden env teststatt testzu Bypass - it), get_mtime in test.c liest struct timespec, und
  • Option-nt verwendet diese Daten
Thomas Dickey
quelle
Danke, gibt mir dies eine modifizierte Zeit, die detaillierter als Sekunden ist? im Begriff herauszufinden ...
Alexander Mills
Ich vermute, dass die Änderungszeit von Dateien selbst mit diesem Tool nicht detaillierter als Sekunden ist.
Alexander Mills
1
Laut dem Quellcode wird verglichen struct timespec, was möglicherweise feinkörniger ist (nicht alle Dateisysteme unterstützen Zeitstempel unter einer Sekunde, aber Sie werden wahrscheinlich das bekommen, was Sie wollten ...)
Thomas Dickey
schön das ist mucho bueno ... akzeptieren akzeptieren akzeptieren
Alexander Mills
scheint auf OSX Sierra zu funktionieren, aber andererseits habe ich hier einige Gnu-Tools installiert. Ich bin mir sicher, dass ich irgendwann ... keine Ahnung
Alexander Mills
1

Beim Testen auf diesem Linux-System. Die übliche Methode zum Testen der Dateizeiten ist die Shell:

[ file1 -nt file2 ] && echo "yes"

Scheint mit Sekunden zu arbeiten. Dadurch, dass die Dateien mit einem Zeitunterschied von weniger als einer Sekunde berührt werden, wird dieser Unterschied nicht erkannt:

$ touch file2; sleep 0.1; touch file1; [ file1 -nt file2 ] && echo "yes"

So bestätigen Sie das Problem (Zeit nach dem Punkt ist Nanosekunden):

$ ls --time-style=full-iso -l file?
-rw-r--r-- 1 user user 0 2017-06-23 01:37:01.707387495 -0400 file1
-rw-r--r-- 1 user user 0 2017-06-23 01:37:01.599392538 -0400 file2

Das file1ist (etwas) neuer als file2.

Das Problem besteht nun darin, den Zeitwert korrekt zu verarbeiten.

Eine Lösung besteht darin, eine formatierte Ausgabe von ls zu verwenden:

$ ls --time-style=+%s.%N -l file?
-rw-r--r-- 1 user user 0 1498196221.707387495 file1
-rw-r--r-- 1 user user 0 1498196221.599392538 file2

Extrahieren der Zeit auf zwei Variablen (ohne Punkt):

$ file1time=$(ls --time-style=+%s%N -l file1 | awk "{print(\$6)}")
$ file2time=$(ls --time-style=+%s%N -l file2 | awk "{print(\$6)}")

Und vergleichen Sie die Zeiten (Zeiten mit Nanosekunden passen kaum in einen 64-Bit-Wert. Wenn Ihr System kein 64-Bit verwendet, schlägt dieser Vergleich fehl):

$ [ $file1time -gt $file2time ] && echo "yes"
yes

Das zeigt, dass file1das neuer ist alsfile2


Wenn lsdas erforderliche Format nicht vorhanden ist, können Sie stat ausprobieren.

$ stat file1
  File: file1
  Size: 0               Blocks: 0          IO Block: 4096   regular file
Device: 805h/2053d      Inode: 9180838     Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/    user)   Gid: ( 1000/    user)
Access: 2017-06-23 01:37:01.707387495 -0400
Modify: 2017-06-23 01:37:01.707387495 -0400
Change: 2017-06-23 01:37:01.707387495 -0400
 Birth: -

Wenn die Ausgabe Nanosekunden anzeigt, benötigen wir das Datum, um die Uhrzeit zu analysieren (und zu formatieren).

$ stat --printf='%y\n' file1
2017-06-23 01:37:01.707387495 -0400

$ date +'%s%N' -d "$(stat --printf='%y\n' file1)" 
1498196221707387495

Der Rest ist der gleiche. Ordnen Sie die Ergebnisse von Datei1 und Datei2 zwei Variablen zu und vergleichen Sie sie numerisch.

Isaac
quelle
Sie sagen also, dass -ot und -nt nur Sekunden zum Vergleich verwenden?
Alexander Mills
sieht so aus, das ist ziemlich lahm, das ist eine gute Antwort
Alexander Mills
Ich denke, es date -rist wahrscheinlich eine bessere Option, und es scheint x-Plattform zu sein
Alexander Mills
1

Wenn Sie bereit sind, nicht eingebettetes Linux anzunehmen, können Sie den testexternen Befehl verwenden, der Teil von GNU coreutils ist. ( testist ein anderer Name für [und ist in den meisten Muscheln eingebaut). Es hat eine Granose von Nanosekunden (bis zu der vom Dateisystem angegebenen Genauigkeit).

/usr/bin/test "$target" -nt "$source"

Der -ntOperator wird nicht von POSIX definiert, ist jedoch in vielen Implementierungen vorhanden, einschließlich dash, bash, pdksh, mksh, ATT ksh, zsh, GNU coreutils testund BusyBox. Viele Implementierungen (dash, bash, pdksh, mksh, BusyBox - getestet auf Debian jessie) unterstützen jedoch nur eine Granularität von 1 Sekunde.

Es wäre jedoch eine bessere Idee, Tools zu verwenden, die für diesen Job vorgesehen sind, z. B. make . Das Ausführen eines Befehls nur, wenn eine bestimmte Datei neuer als eine andere Datei ist, ist der springende Punkt. Mit dem folgenden Inhalt in einer Datei namens Makefile(beachten Sie, dass Sie vor jeder Befehlszeile ein Tabulatorzeichen benötigen).

target: source
    echo This command is only executed if target is newer than source
    do_stuff <source >$@

Ausführen make target, um die Befehle auszuführen, die es generieren. Wenn targetvorhanden und neuer als source, werden die Befehle nicht ausgeführt. Weitere Informationen finden Sie in der Dokumentation zu make.

Gilles 'SO - hör auf böse zu sein'
quelle
schön, ja etwas körnigeres als Sekunden wäre sehr praktisch. Können Sie mit make ein Beispiel hinzufügen?
Alexander Mills
Sie sagen, dass / usr / bin / test anders ist als / usr / bin / bash oder / usr / bin / sh?
Alexander Mills
@AlexanderMills Ich habe ein Beispiel hinzugefügt, empfehle jedoch, einige Dokumentationen oder Tutorials zu lesen. /usr/bin/testund Shell-Builtins testimplementieren größtenteils dieselbe Funktionalität, es kann jedoch geringfügige Unterschiede geben, und die Unterstützung der Auflösung von Zeitstempeln im Subsekundenbereich ist einer dieser geringfügigen Unterschiede.
Gilles 'SO - hör auf böse zu sein'
1

Das GNU-Datum kann die mtime einer Datei so formatieren, dass sie die Nanosekunden seit der Epoche ist:

date +%s%N --reference file

Diese Zahlen sind vergleichbar mit test oder '['. Es funktioniert sogar mit Bash v3.00 auf einem i386 (32-Bit) -System (ca. 2008).

xxx=$(date +%s%N --reference file1)
yyy=$(date +%s%N --reference file2)
[ $xxx -lt $yyy ] && echo file1 is older than file2
jmccanta
quelle
Die date -rOption scheint zu funktionieren, ich denke, das ist das gleiche wiedate--reference
Alexander Mills
0

POSIXly würden Sie verwenden find:

if find "$source_file" -prune -newer "$target_file" | grep -q '^'; then
  printf '%s\n' "$source_file is newer than $target_file"
else
  echo "It's not newer or one of the files is not accessible"
fi

Bei Symlinks vergleicht dies die Zeit der Symlinks selbst. Fügen Sie die Option -Hoder hinzu, um die Ziele der Symlinks zu vergleichen -L.

Dies setzt voraus, dass $source_filees nicht mit -einem der findPrädikate beginnt und ansonsten nicht diesem entspricht . Wenn Sie mit beliebigen Dateinamen umgehen müssen, müssen Sie zunächst folgende Schritte ausführen:

case $source_file in
  (["-+()!"]*) source_file=./$source_file;;
esac

GNU- und FreeBSD- findImplementierungen unterstützen mindestens eine Granularität von weniger als einer Sekunde. AFAICT, macos scheint nicht einmal Informationen im Subsekundenbereich in den Dateiattributen des HFS + -Dateisystems zu speichern.

Stéphane Chazelas
quelle
-1

Unter OSX und Linux date -rscheint es gut zu funktionieren, ich benutze date -r "$file" +%s, um die Änderungszeit in Sekunden seit der Epoche wie folgt zu erhalten:

    is_file_older_than() {

      seconds="$1"; file="$2";

      if [[ ! -f "$file" ]]; then
          return 1;
      fi

      modified_secs="$(date -r "$file" +%s)"
      current_secs="$(date +%s)"
      diff="$(expr "$current_secs" - "$modified_secs")"

      if [[ "$diff" -gt "$seconds" ]]; then
        return 0
      fi

      return 1
    }


   is_file_older_than 55  foobar.txt

Dadurch wird überprüft, ob die Änderungszeit in Sekunden für foobar.txt vor 55 Sekunden liegt.

Die Manpage mit der Option -r: http://man7.org/linux/man-pages/man1/date.1.html

Alexander Mills
quelle