Datei verschieben, aber nur, wenn sie geschlossen ist

10

Ich möchte große Dateien, die von einem externen Prozess erstellt wurden, verschieben, sobald sie geschlossen sind.

Ist dieser Testbefehl korrekt?

if lsof "/file/name"
then
        # file is open, don't touch it!
else
        if [ 1 -eq $? ]
        then
                # file is closed
                mv /file/name /other/file/name
        else
                # lsof failed for some other reason
        fi
fi

BEARBEITEN: Die Datei stellt einen Datensatz dar und ich muss warten, bis er vollständig ist, um ihn zu verschieben, damit ein anderes Programm darauf reagieren kann. Deshalb muss ich wissen, ob der externe Prozess mit der Datei abgeschlossen ist.

Peter Kovac
quelle
3
Randnotiz: Sobald eine Datei geöffnet ist, verwenden Prozesse Dateideskriptoren und Inode-Daten, um sie zu bearbeiten. Das Ändern des Pfads (dh das Verschieben der Datei) verursacht keine allzu großen Probleme.
John WH Smith
2
Haben Sie die Kontrolle über den externen Prozess? Wäre es dem externen Prozess möglich, eine temporäre Datei zu erstellen und die Datei umzubenennen, sobald das Schreiben abgeschlossen ist?
Jenny D
@ JennyD Ich habe einige Nachforschungen angestellt und es stellt sich heraus, dass es wahr ist. Ich brauche lsofüberhaupt nicht Ich muss nur überprüfen, ob die Dateierweiterung nicht ist .tmp. Das macht es trivial. Ich bin jedoch froh, dass ich meine Frage gestellt habe, da ich etwas über lsofund inotifyund so gelernt habe .
Peter Kovac
@PeterKovac Ich habe durch das Lesen der Antworten auch mehr über sie erfahren, daher bin ich sehr froh, dass Sie danach gefragt haben.
Jenny D
@JohnWHSmith - Dies ist normalerweise der Fall, wenn er die Datei innerhalb desselben Dateisystems verschiebt. Wenn er die Datei in ein neues Dateisystem verschiebt, bevor der Writer mit dem Schreiben fertig ist, gehen einige Daten verloren.
Johnny

Antworten:

11

Von der lsofManpage

Lsof gibt eine Eins (1) zurück, wenn ein Fehler festgestellt wurde, einschließlich des Fehlens des Auffindens von Befehlsnamen, Dateinamen, Internetadressen oder -dateien, Anmeldenamen, NFS-Dateien, PIDs, PGIDs oder UIDs, die aufgelistet werden sollen. Wenn die Option -V angegeben ist, gibt lsof die Suchelemente an, die nicht aufgelistet werden konnten.

Das würde also bedeuten, dass Ihre lsof failed for some other reasonKlausel niemals ausgeführt wird.

Haben Sie versucht, die Datei nur zu verschieben, während Ihr externer Prozess sie noch geöffnet hat? Wenn sich das Zielverzeichnis im selben Dateisystem befindet, sollte dies keine Probleme verursachen, es sei denn, Sie müssen von einem dritten Prozess aus unter dem ursprünglichen Pfad darauf zugreifen, da der zugrunde liegende Inode unverändert bleibt. Sonst denke ich mvwird sowieso scheitern.

Wenn Sie wirklich warten müssen, bis Ihr externer Prozess mit der Datei abgeschlossen ist, sollten Sie einen Befehl verwenden, der blockiert, anstatt wiederholt abzufragen. Unter Linux können Sie inotifywaitdies verwenden. Z.B:

 inotifywait -e close_write /path/to/file

Wenn Sie verwenden müssen lsof(möglicherweise aus Gründen der Portabilität), können Sie Folgendes ausprobieren:

until err_str=$(lsof /path/to/file 2>&1 >/dev/null); do
  if [ -n "$err_str" ]; then
    # lsof printed an error string, file may or may not be open
    echo "lsof: $err_str" >&2

    # tricky to decide what to do here, you may want to retry a number of times,
    # but for this example just break
    break
  fi

  # lsof returned 1 but didn't print an error string, assume the file is open
  sleep 1
done

if [ -z "$err_str" ]; then
  # file has been closed, move it
  mv /path/to/file /destination/path
fi

Aktualisieren

Wie von @JohnWHSmith unten erwähnt, würde das sicherste Design immer eine lsofSchleife wie oben verwenden, da es möglich ist, dass mehr als ein Prozess die Datei zum Schreiben geöffnet hat (ein Beispielfall kann ein schlecht geschriebener Indexierungsdämon sein, der Dateien mit dem Lesevorgang öffnet / schreibe Flag, wenn es wirklich schreibgeschützt sein sollte). inotifywaitkann aber immer noch anstelle von Schlaf verwendet werden, ersetzen Sie einfach die Schlaflinie durch inotifywait -e close /path/to/file.

Graeme
quelle
Danke, das wusste ich nicht inotify. Leider ist es nicht auf meiner Box installiert, aber ich bin sicher, dass ich irgendwo ein Paket finden werde. In meiner Bearbeitung finden Sie einen Grund, warum die Datei geschlossen werden muss: Es handelt sich um einen Datensatz, der vollständig sein muss, bevor er weiter verarbeitet werden kann.
Peter Kovac
1
Eine weitere Randnotiz: Während inotifywaitdas Skript verhindert, dass das Skript häufig zwei "abfragt", muss das OP dennoch lsofeine Schleife einchecken : Wenn die Datei zweimal geöffnet wird, kann das einmalige Schließen das inotifyEreignis auslösen , obwohl die Datei noch nicht bereit ist manipuliert (zum Beispiel sleepkönnte Ihr Anruf in Ihrem letzten Codeausschnitt durch ersetzt werden inotifywait).
John WH Smith
@ John a close_writesollte in Ordnung sein, da nur ein Prozess die Datei gleichzeitig zum Schreiben öffnen kann. Es wird davon ausgegangen, dass ein anderer Benutzer es nicht direkt nach dem Schließen öffnet, aber dann besteht das gleiche Problem beim lsofAbrufen.
Graeme
1
@Graeme Während dies im Fall des OP beabsichtigt sein könnte, erlaubt der Kernel, dass eine Datei zweimal zum Schreiben geöffnet wird (in diesem Fall CLOSE_WRITEwird sie zweimal ausgelöst).
John WH Smith
@ John, aktualisiert.
Graeme
4

Als alternativer Ansatz ist dies der perfekte Fall für ein Rohr. Der zweite Prozess verarbeitet die Ausgabe des ersten Prozesses, sobald sie verfügbar ist, anstatt auf den Abschluss des vollständigen Prozesses zu warten:

process1 input_file.dat | process2 > output_file.dat

Vorteile:

  • Im Allgemeinen viel schneller:
    • Muss nicht auf die Festplatte schreiben und von dieser lesen (dies kann vermieden werden, wenn Sie eine Ramdisk verwenden).
    • Sollte die Maschinenressourcen vollständiger nutzen.
  • Keine Zwischendatei zum Entfernen nach Abschluss.
  • Keine komplexe Verriegelung erforderlich, wie im OP.

Wenn Sie keine Möglichkeit haben, eine Pipe direkt zu erstellen, aber GNU-Coreutils haben , können Sie Folgendes verwenden:

tail -F -n +0 input_file.dat | process2 > output_file.dat

Dadurch wird die Eingabedatei von Anfang an gelesen, unabhängig davon, wie weit der erste Vorgang durch das Schreiben der Datei fortgeschritten ist (auch wenn sie noch nicht gestartet oder bereits abgeschlossen ist).

l0b0
quelle
Ja, das wäre die "offensichtliche" Lösung. Leider liegt der Datengenerierungsprozess außerhalb meiner Kontrolle (wird von einem anderen Benutzer ausgeführt).
Peter Kovac
@PeterKovac Das ist irrelevant: cat input_file.dat | process2 output_file.dat
MariusMatutiae
@MariusMatutiae aber catund process2könnte beenden, bevor process1fertig ist. Sie würden nicht blockieren.
cpugeniusmv