Mam ogólne pytanie, które może wynikać z niezrozumienia sposobu obsługi procesów w systemie Linux.
Dla moich celów zamierzam zdefiniować „skrypt” jako fragment kodu bash zapisanego w pliku tekstowym z włączonymi uprawnieniami do wykonywania dla bieżącego użytkownika.
Mam serię skryptów, które wzywają się w tandemie. Dla uproszczenia nazywam je skryptami A, B i C. Skrypt A wykonuje serię instrukcji, a następnie zatrzymuje się, następnie wykonuje skrypt B, następnie zatrzymuje się, a następnie wykonuje skrypt C. Innymi słowy, seria kroków jest mniej więcej tak:
Uruchom skrypt A:
- Seria instrukcji
- Pauza
- Uruchom skrypt B
- Pauza
- Uruchom skrypt C
Wiem z doświadczenia, że jeśli uruchomię skrypt A do pierwszej pauzy, a następnie dokonam zmian w skrypcie B, zmiany te zostaną odzwierciedlone w wykonaniu kodu, gdy pozwolę mu wznowić. Podobnie, jeśli dokonam zmian w skrypcie C, gdy skrypt A jest nadal wstrzymany, pozwól mu kontynuować po zapisaniu zmian, zmiany te zostaną odzwierciedlone w wykonaniu kodu.
Oto prawdziwe pytanie: czy jest jakiś sposób edycji Skryptu A, gdy jest on jeszcze uruchomiony? A może edycja jest niemożliwa po rozpoczęciu jej wykonywania?
źródło
Odpowiedzi:
W Uniksie większość edytorów pracuje, tworząc nowy plik tymczasowy zawierający edytowaną zawartość. Po zapisaniu edytowanego pliku oryginalny plik jest usuwany, a plik tymczasowy zostaje przemianowany na oryginalną nazwę. (Istnieją oczywiście różne zabezpieczenia zapobiegające utracie danych.) Jest to na przykład styl używany przez flagę („na miejscu”)
sed
lubperl
wywoływany z nią-i
, co wcale nie jest tak naprawdę „na miejscu”. Powinno to zostać nazwane „nowym miejscem o starej nazwie”.Działa to dobrze, ponieważ unix zapewnia (przynajmniej dla lokalnych systemów plików), że otwarty plik istnieje, dopóki nie zostanie zamknięty, nawet jeśli zostanie „usunięty” i utworzony zostanie nowy plik o tej samej nazwie. (To nie przypadek, że systemowe wywołanie unixa w celu „usunięcia” pliku nazywa się w rzeczywistości „unlink”.) Ogólnie rzecz biorąc, jeśli interpreter powłoki ma otwarty plik źródłowy, a plik „edytujesz” w sposób opisany powyżej , powłoka nawet nie zobaczy zmian, ponieważ nadal ma otwarty oryginalny plik.
[Uwaga: podobnie jak w przypadku wszystkich komentarzy opartych na standardach, powyższe podlega wielu interpretacjom i istnieją różne przypadki narożne, takie jak NFS. Zachęcamy do wypełniania komentarzy wyjątkami.]
Oczywiście można bezpośrednio modyfikować pliki; jest to po prostu mało wygodne do edycji, ponieważ chociaż możesz zastąpić dane w pliku, nie możesz go usunąć ani wstawić bez przesunięcia wszystkich następujących danych, co wymagałoby sporo przepisywania. Co więcej, podczas wykonywania tego przesunięcia zawartość pliku byłaby nieprzewidywalna i ucierpiałyby procesy, w których plik byłby otwarty. Aby tego uniknąć (na przykład w przypadku systemów baz danych), potrzebujesz zaawansowanego zestawu protokołów modyfikacji i rozproszonych blokad; rzeczy, które znacznie wykraczają poza zakres typowego narzędzia do edycji plików.
Jeśli więc chcesz edytować plik przetwarzany przez powłokę, masz dwie opcje:
Możesz dołączyć do pliku. To zawsze powinno działać.
Możesz zastąpić plik nową zawartością o dokładnie takiej samej długości . Może to działać lub nie, w zależności od tego, czy powłoka już odczytała tę część pliku, czy nie. Ponieważ większość operacji we / wy na plikach obejmuje bufory odczytu, a ponieważ wszystkie znane mi powłoki odczytują całą komendę złożoną przed jej wykonaniem, jest mało prawdopodobne, abyś mógł sobie z tym poradzić. Z pewnością nie byłby niezawodny.
Nie znam żadnego sformułowania w standardzie Posix, które faktycznie wymaga możliwości dołączenia do pliku skryptu podczas wykonywania pliku, więc może nie działać z każdą powłoką zgodną z Posix, a tym bardziej z obecną ofertą prawie- i czasami powłoki zgodne z posix. Więc YMMV. Ale o ile wiem, działa niezawodnie z bash.
Jako dowód, oto „pozbawiona pętli” implementacja niesławnego programu 99 butelek piwa w bash, który wykorzystuje
dd
do nadpisywania i dołączania (nadpisywanie jest prawdopodobnie bezpieczne, ponieważ zastępuje aktualnie wykonywaną linię, która jest zawsze ostatnią linią plik z komentarzem o dokładnie tej samej długości; zrobiłem to, aby wynik końcowy mógł zostać wykonany bez zachowania samodmodyfikującego).źródło
export beer=100
przed uruchomieniem skryptu, działa on zgodnie z oczekiwaniami.bash
przechodzi długą drogę, aby upewnić się, że czyta polecenia tuż przed ich wykonaniem.Na przykład w:
Powłoka będzie czytać skrypt według bloków, więc prawdopodobnie odczyta oba polecenia, zinterpretuje pierwsze, a następnie przejdzie do końca
cmd1
skryptu i ponownie przeczyta skrypt, aby go przeczytaćcmd2
i wykonać.Możesz to łatwo zweryfikować:
(choć patrząc na
strace
wyniki, wydaje się, że robi to trochę bardziej wymyślne rzeczy (np. przeczytaj dane kilka razy, szukaj wstecz ...) niż kiedy próbowałem tego samego kilka lat temu, więc moje powyższe stwierdzenie o szukaniu wstecz może nie dotyczy już nowszych wersji).Jeśli jednak napiszesz skrypt jako:
Powłoka będzie musiała odczytać do zamknięcia
}
, zapisać ją w pamięci i wykonać. Z tego powoduexit
powłoka nie będzie ponownie czytać ze skryptu, dzięki czemu można go bezpiecznie edytować podczas interpretacji powłoki.Alternatywnie podczas edytowania skryptu pamiętaj, aby napisać nową kopię skryptu. Powłoka będzie nadal czytać oryginalną (nawet jeśli zostanie usunięta lub zostanie zmieniona jej nazwa).
Aby to zrobić, zmienić
the-script
, abythe-script.old
skopiowaćthe-script.old
dothe-script
i edytować.źródło
Naprawdę nie ma bezpiecznego sposobu na modyfikację skryptu podczas jego działania, ponieważ powłoka może użyć buforowania do odczytania pliku. Ponadto, jeśli skrypt zostanie zmodyfikowany przez zastąpienie go nowym plikiem, powłoki zwykle będą czytać nowy plik tylko po wykonaniu pewnych operacji.
Często, gdy skrypt jest zmieniany podczas wykonywania, powłoka kończy zgłaszanie błędów składniowych. Wynika to z faktu, że gdy powłoka zamyka i ponownie otwiera plik skryptu, używa przesunięcia bajtu do pliku, aby zmienić położenie po powrocie.
źródło
Możesz obejść ten problem, ustawiając pułapkę na skrypcie, a następnie używając,
exec
aby pobrać nową zawartość skryptu. Zauważ jednak, żeexec
wywołanie rozpoczyna skrypt od zera, a nie od miejsca, w którym dotarł w trakcie procesu, więc skrypt B zostanie wywołany (itd.).To będzie nadal wyświetlać datę na ekranie. Mógłbym wtedy edytować skrypt i zmienić
date
naecho "Date: $(date)"
. Po napisaniu tego uruchomiony skrypt nadal wyświetla tylko datę. Jakkolwiek kiedykolwiek wyślę sygnał, który ustawiłem natrap
przechwytywanie, skrypt będzieexec
(zastępuje bieżący uruchomiony proces określonym poleceniem), którym jest polecenie$CMD
i argumenty$@
. Możesz to zrobić, wydająckill -1 PID
- gdzie PID jest PID uruchomionego skryptu - a dane wyjściowe zmieniają się, aby pokazać sięDate:
przeddate
wyjściem komendy.Możesz zapisać „stan” skryptu w zewnętrznym pliku (w say / tmp) i przeczytać zawartość, aby dowiedzieć się, gdzie „wznowić”, kiedy program zostanie ponownie uruchomiony. Możesz następnie dodać dodatkowe zakończenie pułapek (SIGINT / SIGQUIT / SIGKILL / SIGTERM), aby wyczyścić ten plik tmp, więc po ponownym uruchomieniu po przerwaniu „Skryptu A” rozpocznie się od początku. Wersja stanowa wyglądałaby mniej więcej tak:
źródło
$0
i$@
na początku skryptu i używając tych zmiennych wexec
zamian.