Wie geht Linux mit Shell-Skripten um?


22

Betrachten wir für diese Frage ein Bash-Shell-Skript, obwohl diese Frage auf alle Arten von Shell-Skripten anwendbar sein muss.

Wenn jemand ein Shell-Skript ausführt, lädt Linux das gesamte Skript auf einmal (möglicherweise in den Speicher) oder liest es die Skriptbefehle einzeln (Zeile für Zeile)?

Mit anderen Worten, wenn ich ein Shell-Skript ausführe und es lösche, bevor die Ausführung abgeschlossen ist, wird die Ausführung abgebrochen oder wird sie so fortgesetzt, wie sie ist?


3
Versuch es. (Es geht weiter.)
voraussichtlich

1
@devnull da ist eigentlich eine interessante frage hier. Zugegeben, ob es fortgesetzt wird oder nicht, ist trivial zu testen, aber es gibt Unterschiede zwischen Binärdateien (die in den Speicher geladen werden) und Skripten mit einer Shebang-Zeile oder Skripten ohne Shebang-Zeile.
Terdon


23
Für den Zweck Ihrer eigentlichen Absicht, das Shell-Skript während seiner Ausführung zu löschen, spielt es keine Rolle, ob es auf einmal oder zeilenweise eingelesen wird. Unter Unix wird ein Inode erst gelöscht, wenn die zuletzt geöffnete Datei geschlossen wurde (auch wenn in keinem Verzeichnis eine Verknüpfung zu ihm besteht). Mit anderen Worten, auch wenn Ihre Shell das Shell-Skript während der Ausführung zeilenweise einliest, ist es sicher, es zu löschen. Die einzige Ausnahme ist, wenn Ihre Shell das Shell-Skript jedes Mal schließt und erneut öffnet. Wenn dies jedoch der Fall ist, treten viel größere (Sicherheits-) Probleme auf.
Chris Jester-Young

Antworten:


33

Wenn Sie verwenden strace, können Sie sehen, wie ein Shell-Skript ausgeführt wird, wenn es ausgeführt wird.

Beispiel

Angenommen, ich habe dieses Shell-Skript.

$ cat hello_ul.bash 
#!/bin/bash

echo "Hello Unix & Linux!"

Ausführen mit strace:

$ strace -s 2000 -o strace.log ./hello_ul.bash
Hello Unix & Linux!
$

Ein Blick in die strace.logDatei zeigt Folgendes.

...
open("./hello_ul.bash", O_RDONLY)       = 3
ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7fff0b6e3330) = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
read(3, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 80) = 40
lseek(3, 0, SEEK_SET)                   = 0
getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0
fcntl(255, F_GETFD)                     = -1 EBADF (Bad file descriptor)
dup2(3, 255)                            = 255
close(3)     
...

Sobald die Datei eingelesen wurde, wird sie ausgeführt:

...
read(255, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 40) = 40
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc0b38ba000
write(1, "Hello Unix & Linux!\n", 20)   = 20
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
read(255, "", 40)                       = 0
exit_group(0)                           = ?

Oben sehen wir deutlich, dass das gesamte Skript als eine einzige Entität eingelesen und danach ausgeführt wird. So würde es zumindest in Bashs Fall "erscheinen", dass es die Datei einliest und sie dann ausführt. Sie denken also, Sie könnten das Skript bearbeiten, während es ausgeführt wird?

HINWEIS: Tun Sie es jedoch nicht! Lesen Sie weiter, um zu verstehen, warum Sie sich nicht mit einer laufenden Skriptdatei anlegen sollten.

Was ist mit anderen Dolmetschern?

Aber deine Frage ist nicht ganz richtig. Es ist nicht Linux, das notwendigerweise den Inhalt der Datei lädt, sondern der Interpreter, der den Inhalt lädt. Es hängt also wirklich von der Implementierung des Interpreters ab, ob die Datei vollständig oder in Blöcken oder Zeilen gleichzeitig geladen wird.

Warum können wir die Datei nicht bearbeiten?

Wenn Sie jedoch ein viel größeres Skript verwenden, werden Sie feststellen, dass der obige Test etwas irreführend ist. Tatsächlich laden die meisten Interpreter ihre Dateien in Blöcken. Dies ist bei vielen Unix-Tools Standard, bei denen Blöcke einer Datei geladen, verarbeitet und dann ein anderer Block geladen wird. Sie können dieses Verhalten anhand dieser Fragen und Antworten zu U & L sehen, die ich vor einiger Zeit grepmit dem Titel geschrieben habe: Wie viel Text verbraucht grep / egrep jedes Mal? .

Beispiel

Angenommen, wir erstellen das folgende Shell-Skript.

$ ( 
    echo '#!/bin/bash'; 
    for i in {1..100000}; do printf "%s\n" "echo \"$i\""; done 
  ) > ascript.bash;
$ chmod +x ascript.bash

Ergebnis in dieser Datei:

$ ll ascript.bash 
-rwxrwxr-x. 1 saml saml 1288907 Mar 23 18:59 ascript.bash

Welche enthält die folgenden Arten von Inhalten:

$ head -3 ascript.bash ; echo "..."; tail -3 ascript.bash 
#!/bin/bash
echo "1"
echo "2"
...
echo "99998"
echo "99999"
echo "100000"

Wenn Sie dies mit der gleichen Technik wie oben ausführen, gilt Folgendes strace:

$ strace -s 2000 -o strace_ascript.log ./ascript.bash
...    
read(255, "#!/bin/bash\necho \"1\"\necho \"2\"\necho \"3\"\necho \"4\"\necho \"5\"\necho \"6\"\necho \"7\"\necho \"8\"\necho \"9\"\necho \"10\"\necho 
...
...
\"181\"\necho \"182\"\necho \"183\"\necho \"184\"\necho \"185\"\necho \"186\"\necho \"187\"\necho \"188\"\necho \"189\"\necho \"190\"\necho \""..., 8192) = 8192

Sie werden feststellen, dass die Datei in Schritten von 8 KB eingelesen wird, sodass Bash und andere Shells eine Datei wahrscheinlich nicht vollständig laden, sondern in Blöcken einlesen.

Verweise


@terdon - ja, ich erinnere mich, dass ich diese Fragen und Antworten schon einmal gesehen habe.
SLM

5
Mit einem 40-Byte-Skript wird es sicher in einem Block gelesen. Versuche es mit einem> 8kB Skript.
Gilles 'SO- hör auf böse zu sein'

Ich habe es nie versucht, aber ich denke, das Entfernen von Dateien wird erst dann durchgeführt, wenn alle Prozesse den mit der entfernten Datei verknüpften Dateideskriptor schließen. Daher liest bash möglicherweise weiter aus der entfernten Datei.
Farid Nouri Neshat

@ Gilles - ja, ich habe ein Beispiel hinzugefügt, war dabei.
SLM

2
Dieses Verhalten ist versionsabhängig. Ich habe mit bash Version 3.2.51 (1) -release getestet und festgestellt, dass es nicht über die aktuelle Zeile hinaus gepuffert hat (siehe diese Stackoverflow-Antwort ).
Gordon Davisson

11

Dies ist mehr von der Shell abhängig als vom Betriebssystem.

Abhängig von der Version, kshLesen Sie das Skript bei Bedarf in 8k- oder 64k-Byte-Blöcken.

bashLesen Sie das Skript Zeile für Zeile. Angesichts der Tatsache, dass die Zeilen beliebig lang sein können, werden jedes Mal 8176 Bytes ab dem Beginn der nächsten zu analysierenden Zeile gelesen.

Dies ist für einfache Konstruktionen, dh eine Reihe von einfachen Befehlen.

Wenn Shell-strukturierte Befehle verwendet werden ( ein Fall, den die akzeptierte Antwort nicht berücksichtigt ), wie eine for/do/doneSchleife, acase/esac Schalter, ein Here-Dokument, eine in Klammern gesetzte Subshell, eine Funktionsdefinition usw. und eine beliebige Kombination der oben genannten, werden Shell-Interpreter gelesen Stellen Sie am Ende der Konstruktion zunächst sicher, dass kein Syntaxfehler vorliegt.

Dies ist etwas ineffizient, da derselbe Code immer wieder häufig gelesen werden kann, jedoch dadurch gemindert wird, dass dieser Inhalt normalerweise zwischengespeichert wird.

Unabhängig vom Shell-Interpreter ist es sehr unklug, ein Shell-Skript zu ändern, während es ausgeführt wird, da die Shell jeden Teil des Skripts erneut lesen kann. Dies kann zu unerwarteten Syntaxfehlern führen, wenn es nicht synchron ist.

Beachten Sie auch, dass Bash möglicherweise mit einer Segmentierungsverletzung abstürzt, wenn es nicht in der Lage ist, eine zu große Skriptkonstruktion zu speichern. Ksh93 kann fehlerfrei lesen.


7

Das hängt davon ab, wie der Interpreter arbeitet, der das Skript ausführt. Der Kernel merkt lediglich, dass die auszuführende Datei mit beginnt#! , führt den Rest der Zeile als Programm aus und gibt ihr die ausführbare Datei als Argument. Wenn der dort aufgeführte Interpreter diese Datei zeilenweise liest (wie interaktive Shells es mit Ihrer Eingabe tun), erhalten Sie dies (aber mehrzeilige Schleifenstrukturen werden gelesen und zum Wiederholen beibehalten); Wenn der Interpreter die Datei in den Speicher schlürft, verarbeitet (kompiliert sie möglicherweise zu einer Zwischendarstellung wie Perl und Pyton), wird die Datei vor der Ausführung vollständig gelesen.

Wenn Sie die Datei in der Zwischenzeit löschen, wird sie erst gelöscht, wenn der Interpreter sie schließt (wie immer verschwinden Dateien, wenn der letzte Verweis, sei es ein Verzeichniseintrag oder ein Prozess, der sie offen hält).


4

Die 'x'-Datei:

cat<<'dog' >xyzzy
LANG=C
T=`tty`
( sleep 2 ; ls -l xyzzy >$T ) &
( sleep 4 ; rm -v xyzzy >$T ) &
( sleep 4 ; ls -l xyzzy >$T ) &
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
dog

sh xyzzy

Der Lauf:

~/wrk/tmp$ sh x
alive.
alive.
alive.
-rw-r--r-- 1 yeti yeti 287 Mar 23 16:57 xyzzy
alive.
removed `xyzzy'
ls: cannot access xyzzy: No such file or directory
alive.
alive.
alive.
alive.
~/wrk/tmp$ _

IIRC Eine Datei wird nicht gelöscht, solange sie von einem Prozess geöffnet bleibt. Durch das Löschen wird nur der angegebene DIRENT entfernt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.