Przenieś plik, ale tylko jeśli jest zamknięty

10

Chcę przenieść duży plik utworzony przez proces zewnętrzny, jak tylko zostanie zamknięty.

Czy to polecenie testowe jest prawidłowe?

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

EDYCJA: plik reprezentuje zestaw danych i muszę czekać, aż zostanie ukończony, aby go przenieść, aby inny program mógł na nim działać. Dlatego muszę wiedzieć, czy proces zewnętrzny jest wykonywany z plikiem.

Peter Kovac
źródło
3
Uwaga dodatkowa: po otwarciu pliku procesy używają deskryptorów plików i danych i-węzłów, aby nim manipulować. Zmiana ścieżki (tj. Przeniesienie pliku) nie spowoduje zbyt dużego problemu w procesie.
John WH Smith
2
Czy masz kontrolę nad procesem zewnętrznym? Czy proces zewnętrzny mógłby utworzyć plik tymczasowy i zmienić nazwę pliku po zakończeniu zapisywania do niego?
Jenny D,
@JennyD Przeprowadziłem dochodzenie i okazało się, że to prawda. Nie potrzebuję lsofwcale, muszę tylko sprawdzić, czy rozszerzenie pliku nie jest .tmp. To sprawia, że ​​jest to trywialne. Jednak cieszę się, że zapytałem moje pytanie, od kiedy dowiedział się trochę o lsofi inotifyi rzeczy.
Peter Kovac
@PeterKovac Nauczyłem się również o nich więcej, czytając odpowiedzi, więc cieszę się, że o to pytałeś.
Jenny D,
@JohnWHSmith - Zwykle jest to prawdą, jeśli przenosisz plik w tym samym systemie plików, jeśli przeniesie plik do nowego systemu plików, zanim program piszący skończy do niego zapisywać, utraci część danych.
Johnny

Odpowiedzi:

11

Od lsofstrony man

Lsof zwraca jeden (1), jeśli wykryty zostanie jakikolwiek błąd, w tym błąd w zlokalizowaniu nazw poleceń, nazw plików, adresów lub plików internetowych, nazw logowania, plików NFS, PID, PGID lub UID, o których wyświetlenie został poproszony. Jeśli podano opcję -V, lsof wskaże elementy wyszukiwania, których nie udało się wyświetlić na liście.

To sugerowałoby, że twoja lsof failed for some other reasonklauzula nigdy nie zostanie wykonana.

Czy próbowałeś właśnie przenieść plik, gdy proces zewnętrzny ma go jeszcze otwarty? Jeśli katalog docelowy znajduje się w tym samym systemie plików, nie powinno być z tym żadnych problemów, chyba że musisz uzyskać dostęp do oryginalnej ścieżki z trzeciego procesu, ponieważ i-węzeł pozostanie taki sam. W przeciwnym razie myślę, że mvi tak się nie uda.

Jeśli naprawdę musisz poczekać, aż proces zewnętrzny zakończy się plikiem, lepiej użyć polecenia blokującego zamiast wielokrotnego odpytywania. W systemie Linux możesz inotifywaitdo tego użyć . Na przykład:

 inotifywait -e close_write /path/to/file

Jeśli musisz użyć lsof(być może do przenoszenia), możesz wypróbować coś takiego:

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

Aktualizacja

Jak zauważył @JohnWHSmith poniżej, najbezpieczniejszy projekt zawsze używałby lsofpętli jak wyżej, ponieważ możliwe jest, że więcej niż jeden proces otworzyłby plik do zapisu (przykładowym przypadkiem może być źle napisany demon indeksujący, który otwiera pliki z odczytem / napisz flagę, kiedy powinna być tylko do odczytu). inotifywaitnadal można go używać zamiast snu, wystarczy wymienić linię snu na inotifywait -e close /path/to/file.

Graeme
źródło
Dzięki, nie byłam tego świadoma inotify. Niestety nie jest zainstalowany na moim pudełku, ale jestem pewien, że gdzieś znajdę paczkę. Zobacz moją edycję z powodów, dla których muszę zamknąć plik: jest to zestaw danych i musi być kompletny przed dalszym przetwarzaniem.
Peter Kovac
1
Kolejna uwaga: chociaż inotifywaitzapobiegnie to często „odpytywaniu” skryptu, OP nadal musi sprawdzać lsofw pętli: jeśli plik zostanie otwarty dwukrotnie, zamknięcie raz może wywołać inotifyzdarzenie, nawet jeśli plik nie jest gotowy do zmanipulowane (na przykład w ostatnim fragmencie kodu twoje sleepwywołanie może zostać zastąpione inotifywait).
John WH Smith
@John close_writepowinien być w porządku, ponieważ tylko jeden proces może mieć otwarty plik do zapisu na raz. Zakłada się, że inny nie otworzy go zaraz po zamknięciu, ale wtedy ten sam problem występuje z lsofodpytywaniem.
Graeme
1
@Graeme Chociaż może tak być z założenia w przypadku OP, jądro umożliwia dwukrotne otwarcie pliku do zapisu (w takim przypadku CLOSE_WRITEuruchamiane jest dwukrotnie).
John WH Smith
@John, zaktualizowano.
Graeme
4

Jako alternatywne podejście jest to idealny przypadek dla potoku - drugi proces przetworzy dane wyjściowe z pierwszego procesu, gdy tylko będzie dostępny, zamiast czekać na zakończenie pełnego procesu:

process1 input_file.dat | process2 > output_file.dat

Zalety:

  • Ogólnie znacznie szybciej:
    • Nie musi zapisywać i odczytywać z dysku (można tego uniknąć, jeśli używasz ramdysku).
    • Powinien pełniej wykorzystywać zasoby maszynowe.
  • Brak pliku pośredniego do usunięcia po zakończeniu.
  • Nie jest wymagane złożone blokowanie, jak w OP.

Jeśli nie masz możliwości bezpośredniego utworzenia potoku, ale masz jądra GNU , możesz użyć tego:

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

Spowoduje to rozpoczęcie odczytu pliku wejściowego od początku, bez względu na to, jak daleko zajdzie pierwszy proces poprzez zapisanie pliku (nawet jeśli nie został jeszcze uruchomiony lub już zakończony).

l0b0
źródło
Tak, to byłoby „oczywiste” rozwiązanie. Niestety proces generowania danych jest poza moją kontrolą (prowadzony przez innego użytkownika).
Peter Kovac
@PeterKovac To nie ma znaczenia: cat plik_wejściowy.dat | process2 output_file.dat
MariusMatutiae
@MariusMatutiae ale cati process2kończy przed process1zakończeniem. Nie zablokowaliby się.
cpugeniusmv