UWAGA: @ jw013 zgłasza następujący nieobsługiwany sprzeciw w komentarzach poniżej:
Wadą jest to, że kod samodmodyfikujący jest ogólnie uważany za złą praktykę. W dawnych czasach małych programów asemblacyjnych był to sprytny sposób na ograniczenie gałęzi warunkowych i poprawę wydajności, ale obecnie zagrożenia bezpieczeństwa przeważają nad zaletami. Twoje podejście nie zadziałałoby, gdyby użytkownik, który uruchomił skrypt, nie miał uprawnień do zapisu na skrypcie.
Odpowiedziałem mu zarzutów zabezpieczeń, wskazując, że jakieś specjalne uprawnienia są wymagane tylko raz na instalacji / aktualizacji działań w celu instalacji / aktualizacji na samodzielne instalowanie skryptu - co ja osobiście nazywam całkiem bezpieczne. Wskazałem mu także man sh
odniesienie do osiągania podobnych celów za pomocą podobnych środków. W tym czasie nie zadałem sobie trudu, aby wskazać, że niezależnie od wad bezpieczeństwa lub ogólnie nieadekwatnych praktyk, które mogą, ale nie muszą być reprezentowane w mojej odpowiedzi, były one bardziej zakorzenione w samym pytaniu niż w mojej odpowiedzi na to:
Jak skonfigurować shebang, aby uruchamianie skryptu jako /path/to/script.sh zawsze używa Zsh dostępnego w PATH?
Niezadowolony @ jw013 nadal sprzeciwił się, przekazując swój dotychczas nieobsługiwany argument co najmniej kilkoma błędnymi stwierdzeniami:
Używasz jednego pliku, a nie dwóch plików. [ man sh
Odniesienie]
Opakowanie ma jeden plik modyfikacji innego pliku. Plik się modyfikuje. Istnieje wyraźna różnica między tymi dwoma przypadkami. Plik, który pobiera dane wejściowe i generuje dane wyjściowe, jest w porządku. Plik wykonywalny, który zmienia się w trakcie działania, jest ogólnie złym pomysłem. Przykład, na który wskazałeś, tego nie robi.
Na pierwszym miejscu:
JEDYNY WYKONALNY KOD W KAŻDYM WYKONALNYM SKRIPIE ŁUSKI JEST TO #!
SAM
(chociaż nawet oficjalnie nie#!
jest określony )
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
{ ${l=lsof -p} $$
echo "$l \$$" | sh
} | grep \
"COMMAND\|^..*sh\| [0-9]*[wru] "
#END
FILE
##OUTPUT
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
file 8900 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
file 8900 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
file 8900 mikeserv 0r REG 0,35 108 15496912 /tmp/zshUTTARQ (deleted)
file 8900 mikeserv 1u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 255r REG 0,33 108 2134129 /home/mikeserv/file
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sh 8906 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
sh 8906 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
sh 8906 mikeserv 0r FIFO 0,8 0t0 15500515 pipe
sh 8906 mikeserv 1w FIFO 0,8 0t0 15500514 pipe
sh 8906 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
{ sed -i \
'1c#!/home/mikeserv/file' ./file
./file
sh -c './file ; echo'
grep '#!' ./file
}
##OUTPUT
zsh: too many levels of symbolic links: ./file
sh: ./file: /home/mikeserv/file: bad interpreter: Too many levels of symbolic links
#!/home/mikeserv/file
Skrypt powłoki jest tylko plik tekstowy - aby mogła ona mieć żadnego wpływu na wszystko należy czytać przez innego pliku wykonywalnego, jego instrukcje następnie interpretowane przez tego innego pliku wykonywalnego, zanim wreszcie inny plik wykonywalny następnie wykonuje swoją interpretację z następujących Skrypt powłoki. Jest to możliwe za wykonanie skryptu powłoki, aby zaangażować mniej niż dwa pliki. Istnieje możliwy wyjątek we zsh
własnym kompilatorze, ale dzięki temu mam niewielkie doświadczenie i nie ma tu żadnej reprezentacji.
Hashang skryptu powłoki musi wskazywać na zamierzonego interpretera lub zostać odrzucony jako nieistotny.
Powłoka ma dwa podstawowe tryby parsowania i interpretacji danych wejściowych: albo bieżące dane wejściowe definiują a, <<here_document
albo definiują { ( command |&&|| list ) ; } &
- innymi słowy, powłoka interpretuje token jako ogranicznik polecenia, które powinno wykonać po przeczytaniu w lub jako instrukcje, aby utworzyć plik i zamapować go na deskryptorze pliku dla innego polecenia. Otóż to.
Podczas interpretacji poleceń w celu wykonania powłoki rozgraniczają tokeny na zestawie słów zastrzeżonych. Gdy powłoka napotyka otwór żeton musi czytać dalej na liście poleceń aż lista jest albo ograniczona przez zamknięciem tokena takie jak nowej linii - w stosownych przypadkach - czy zamknięcie tokena jak })
na ({
przed egzekucją.
Powłoka rozróżnia proste i złożone polecenie. Polecenie złożone jest zbiorem poleceń, które należy odczytać przed wykonaniem, ale powłoka nie wykonuje $expansion
żadnej z prostych składowych poleceń, dopóki nie wykona pojedynczo każdego z nich.
Tak więc w poniższym przykładzie ;semicolon
słowa zastrzeżone ograniczają poszczególne proste polecenia, podczas gdy \newline
znak, który nie jest klawiszem ucieczki, rozgranicza między dwoma poleceniami złożonymi:
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
echo "simple command ${sc=1}" ;\
: > $0 ;\
echo "simple command $((sc+2))" ;\
sh -c "./file && echo hooray"
sh -c "./file && echo hooray"
#END
FILE
##OUTPUT
simple command 1
simple command 3
hooray
Jest to uproszczenie wytycznych. Staje się znacznie bardziej skomplikowane, gdy weźmie się pod uwagę wbudowane powłoki, podpowłoki, bieżące środowisko itp., Ale dla moich celów tutaj wystarczy.
Mówiąc o wbudowanych i listach poleceń, a function() { declaration ; }
jest jedynie sposobem na przypisanie polecenia złożonego do prostego polecenia. Powłoka nie może wykonywać żadnych $expansions
instrukcji w samej deklaracji - aby ją uwzględnić <<redirections>
- ale musi przechowywać definicję jako pojedynczy, dosłowny ciąg znaków i wykonywać ją jako specjalną powłokę wbudowaną po wywołaniu.
Tak więc funkcja powłoki zadeklarowana w wykonywalnym skrypcie powłoki jest przechowywana w pamięci powłoki tłumaczącej w postaci dosłownej - nie jest rozszerzana o dołączone tutaj dokumenty jako dane wejściowe - i wykonywana niezależnie od pliku źródłowego za każdym razem, gdy jest wywoływana jako wbudowana powłoka - tak długo, jak trwa obecne środowisko powłoki.
Operatory przekierowania <<
i <<-
oba pozwalają na przekierowanie wierszy zawartych w pliku wejściowym powłoki, znanym jako dokument tutaj, na wejście polecenia.
Niniejszy dokument należy traktować jako pojedyncze słowo, które zaczyna się po następnym \newline
i trwa do momentu, aż pojawi się wiersz zawierający tylko ogranicznik i literę „ a” \newline
, bez [:blank:]
znaku s pomiędzy nimi. Następnie rozpoczyna się następny dokument tutaj , jeśli taki istnieje. Format jest następujący:
[n]<<word
here-document
delimiter
... gdzie opcjonalny n
reprezentuje numer deskryptora pliku. Jeśli numer zostanie pominięty, dokument tutaj odnosi się do standardowego wejścia (deskryptor pliku 0).
for shell in dash zsh bash sh ; do sudo $shell -c '
{ readlink /proc/self/fd/3
cat <&3
} 3<<-FILE
$0
FILE
' ; done
#OUTPUT
pipe:[16582351]
dash
/tmp/zshqs0lKX (deleted)
zsh
/tmp/sh-thd-955082504 (deleted)
bash
/tmp/sh-thd-955082612 (deleted)
sh
Zobaczysz? Dla każdej powłoki powyżej powłoki tworzy plik i mapuje go na deskryptor pliku. W zsh, (ba)sh
powłoce tworzy się zwykły plik /tmp
, zrzuca dane wyjściowe, mapuje je na deskryptor, a następnie usuwa /tmp
plik, aby kopia deskryptora jądra pozostała. dash
unika tych bzdur i po prostu upuszcza przetwarzanie danych wyjściowych do anonimowego |pipe
pliku skierowanego na <<
cel przekierowania .
To sprawia dash
, że:
cmd <<HEREDOC
$(cmd)
HEREDOC
funkcjonalnie równoważny do bash
:
cmd <(cmd)
podczas gdy dash
implementacja jest przynajmniej przenośna POSIXly.
KTÓRE TWORZĄ KILKA PLIKÓW
Więc w odpowiedzi poniżej, kiedy to robię:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_fn() { printf '#!' ; command -v zsh ; cat
} <<SCRIPT >$0
[SCRIPT BODY]
SCRIPT
_fn ; exec $0
FILE
Zdarza się:
Po raz pierwszy cat
zawartość co plik powłoka utworzona na FILE
do ./file
sprawiają, że plik wykonywalny, a następnie uruchom go.
Jądro interpretuje #!
wywołania i /usr/bin/sh
z <read
deskryptorem pliku przypisanym do ./file
.
sh
odwzorowuje ciąg do pamięci składający się z polecenia złożonego rozpoczynającego się _fn()
i kończącego się na SCRIPT
.
Kiedy _fn
nazywa, sh
musi najpierw interpretują następnie mapę do deskryptora pliku zdefiniowanego w <<SCRIPT...SCRIPT
przed powołując _fn
jako specjalny wbudowane narzędzie, ponieważ SCRIPT
jest _fn
„s<input.
Wyjście przez ciągi printf
i command
są wypisywane do _fn
„s Standard-out >&1
- który jest przekierowany do prądu powłoki użytkownika ARGV0
- albo $0
.
cat
łączy swój deskryptor pliku ze <&0
standardowym wejściem - SCRIPT
- >
z ARGV0
argumentem obciętej bieżącej powłoki , lub $0
.
Wypełnianie już wczytanej bieżącej komendy złożonej , sh exec
jest wykonywalnym - i nowo przepisanym - $0
argumentem.
Od czasu ./file
wywołania, aż zawarte w nim instrukcje określają, że powinien on być exec
ponownie, sh
odczytuje je pojedynczym złożonym poleceniem naraz, gdy je wykonuje, podczas gdy ./file
sam nic nie robi, poza radością przyjmując jego nową zawartość. Pliki, które faktycznie działają, są/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
DZIĘKI PO WSZYSTKIM
Więc kiedy @ jw013 określa, że:
Plik, który pobiera dane wejściowe i generuje dane wyjściowe, jest w porządku ...
... wśród swojej błędnej krytyki tej odpowiedzi tak naprawdę nieświadomie akceptuje jedyną zastosowaną tutaj metodę, która w zasadzie polega na:
cat <new_file >old_file
ODPOWIEDŹ
Wszystkie odpowiedzi tutaj są dobre, ale żadna z nich nie jest w pełni poprawna. Wydaje się, że wszyscy twierdzą, że nie można dynamicznie i trwale podążać ścieżką #!bang
. Oto demonstracja utworzenia niezależnego shebang od ścieżki:
PRÓBNY
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me ; exec $0
FILE
WYNIK
$0 : ./file
lines : 13
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
12 > SCRIPT
13 > _rewrite_me ; out=$0 _rewrite_me ; exec $0
$0 : /home/mikeserv/file
lines : 8
!bang : #!/usr/bin/zsh
shell : /usr/bin/zsh
1 > #!/usr/bin/zsh
2 > printf "
...
7 > sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
8 > sed -e 'N;s/\n/ >\t/' -e 4a\\...
Zobaczysz? Sprawiamy, że skrypt sam się nadpisuje. I zdarza się to tylko raz po git
synchronizacji. Od tego momentu ma właściwą ścieżkę w linii #! Bang.
Teraz prawie wszystko to po prostu puch. Aby to zrobić bezpiecznie, potrzebujesz:
Funkcja zdefiniowana u góry i wywołana u dołu, która wykonuje zapis. W ten sposób przechowujemy wszystko, czego potrzebujemy w pamięci i upewniamy się, że cały plik został wczytany, zanim zaczniemy nad nim zapisywać.
Jakiś sposób na określenie ścieżki. command -v
jest całkiem dobry do tego.
Heredocs naprawdę pomagają, ponieważ są to rzeczywiste pliki. W międzyczasie będą przechowywać Twój skrypt. Możesz także używać ciągów, ale ...
Musisz upewnić się, że powłoka odczytuje polecenie, które zastępuje skrypt na tej samej liście poleceń, co ta, która go wykonuje.
Popatrz:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me
exec $0
FILE
Zauważ, że przesunąłem exec
polecenie tylko o jedną linię. Teraz:
#OUTPUT
$0 : ./file
lines : 14
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
13 > _rewrite_me ; out=$0 _rewrite_me
14 > exec $0
Nie dostaję drugiej połowy wyniku, ponieważ skrypt nie może odczytać następnego polecenia. Mimo to, ponieważ jedynym brakującym poleceniem było ostatnie:
cat ./file
#!/usr/bin/zsh
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
Skrypt przyszedł tak, jak powinien - głównie dlatego, że wszystko było w heredoc - ale jeśli nie zaplanujesz go poprawnie, możesz obciąć strumień plików, co przydarzyło mi się powyżej.
env
nie ma go w katalogu / bin i / usr / bin? Spróbujwhich -a env
potwierdzić.