cd im Shellscript - geht doch!

Juli 2020

Das Problem ist nicht neu

Man schreibt ein Shellscript das z.B. einen neuen Pfad anlegt und diesen den Suchpfaden zufügt. Dann möchte man auch das neue Verzeichnis als Arbeitsverzeichnis verwenden. Das Shellscript führt korrekt ein cd-Kommando aus und - am Ende ist man nach wie vor im vorherigen Arbeitsverzeichnis.
Das folgende kleine Beispiel demonstriert das Problem:
#
# gehe-zu-C.sh
#

mkdir -p $HOME/A/B/C             # Pfad anlegen, falls er nicht existiert
export PATH=$HOME/A/B/C:$PATH    # den Suchpfaden voranstellen
cd $HOME/A/B/C                   # dahin wechseln
echo "Ich bin in $(pwd)"
Nun führen wir das Script aus.
otto@DESKTOP:~
$ ./gehe-zu-C.sh
Ich bin in /home/otto/A/B/C
otto@DESKTOP:~
$ pwd
/home/otto

- Aufruf des Shellscripts
- die Ausgabe des Shellscripts
- Script ist beendet
- Was ist unser Arbeitsverzeichnis?
- nach wie vor das Homeverzeichnis
Was ist passiert?
  • Das Script (wie jedes andere) wurde in einer Subshell ausgeführt
  • Der Verzeichnispfad wurde korrekt erstellt
  • Der Suchpfad wurde gesetzt - in der Umgebung der Subshell
  • Wechsel ins neue Verzeichnis - fixiert in der Umgebung der Subshell
Jedoch nach Ende des Shellscripts ist auch die Umgebung der Subshell fort und die letzten beiden Aktionen haben auf die aktuelle Shell keine Wirkung. Hier bleibt alles wie zuvor.

Mit dem Punkt-Kommando (oder source) geht es

Das ist nicht neu. Ein Shellscript kann man mit dem internen Kommando source ausführen. Dadurch wird ein Shellscript von der aktuellen Shell interpretiert und nicht in einer Subshell. Aktionen, die die Umgebung der Shell betreffen, werden nun in der Umgebung der aktuellen Shell ausgeführt.
$ source gehe-zu-C.sh
Ich bin in /home/otto/A/B/C
otto@DESKTOP:~/A/B/C
$ pwd
/home/otto/A/B/C
Anstelle source kann man einfach einen Punkt schreiben, was wohl die meisten so machen.
$ . gehe-zu-C.sh
Mit dieser Lösung kann man sicher leben.

Es geht noch einfacher - mit einer Shellfunktion

Eine Shellfunktion wird in der Umgebung der aktuellen Shell ausgeführt. Darin müsste auch ein cd-Kommando die erhoffte Wirkung haben.
Versuchen wir, anstelle eines Shellscript eine Shellfunktion zu verwenden.
GeheZuC() {
  mkdir -p $HOME/A/B/C
  export PATH=$HOME/A/B/C:$PATH
  cd $HOME/A/B/C
  echo "Ich bin in $(pwd)"
}
Eine Shellfunktion steht wie ein Shellscript dauerhaft zur Verfügung, wenn man sie ins Startscript der Shell einschreibt, also in die Datei .profile (oder .bash_profile oder noch eine andere, je nach Installation).
Der neue Eintrag wird erst nach erneutem Einloggen wirksam, und dann steht der Name der Shellfunktion wie ein internes Kommando zur Verfügung.
otto@DESKTOP:~
$ GeheZuC
Ich bin in /home/otto/A/B/C
otto@DESKTOP:~/A/B/C
$ pwd
/home/otto/A/B/C

Warum nicht mit einer Subshell arbeiten?

Ob es immer eine gute Idee ist, die aktuelle Shell zu beeinflussen, hängt vom konkreten Fall ab. Vielleicht ist das Weiterarbeiten in einer Subshell auch keine schlechte Idee.
Kehren wir gedanklich zum Anfang zurück. Wenn ein Shellscript endet, dann endet auch die Subshell und steht damit nicht als interaktive Shell zur Verfügung.
Hier hilft das interne Kommando exec weiter.
Das Kommando exec wird üblicherweise verwendet, Datenkanäle zu erstellen und/oder umzuleiten. Es kann aber auch den Shellprozess durch einen anderen Prozess ersetzen. Wir erweitern das obige Shellscript wie folgt.
#
# gehe-zu-C.sh
# Version 2

mkdir -p $HOME/A/B/C
export PATH=$HOME/A/B/C:$PATH
cd $HOME/A/B/C
echo "Ich bin in $(pwd)"

# jetzt aktuelle Shell ersetzen
exec /usr/bin/bash -login
Wir ersetzen die aktuelle Shell durch eine neue Login-Shell. Diese erhält die gleichen Eigenschaften wie die Shell nach dem Einloggen. Außerdem gehen die Definitionen des Shellscripts ebenfalls in die neue Login-Shell ein.
Achtung!
Wie genau man eine Login-Shell startet, hängt von der Installation im Systems ab. In meinem System war die Angabe /usr/bin/bash -login richtig, weil die Bash eben unter /usr/bin/ installiert war. Es ist also der aktuelle Installationspfad der Shell zu verwenden. Es könnte auch sein, dass das Argument -login weggelassen werden kann. Eine Login-Shell arbeitet in jedem Fall die Startscripte ab, was man meist schon am Prompt erkennen kann.
Aufruf des veränderten Shellscripts:
otto@DESKTOP:~
$ ./gehe-zu-C.sh
Ich bin in /home/otto/A/B/C
otto@DESKTOP:~/A/B/C
$ pwd
/home/otto/A/B/C
otto@DESKTOP:~/A/B/C
$ ps -l
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1000     8     7  0  80   0 -  4553 -      tty1     00:00:00 bash
0 S  1000    79     8  1  80   0 -  4526 -      tty1     00:00:00 bash
0 R  1000    94    79  0  80   0 -  4642 -      tty1     00:00:00 ps
Der ps-Aufruf zeigt an, dass die interaktive Shell danach tatsächlich eine Subshell ist.
Wir können darin ganz normal weiterarbeiten, und wenn man zurückmöchte, genügt ein exit um die Subshell zu beenden und wir kehren zur ursprünglichen Umgebung zurück.
otto@DESKTOP:~/A/B/C
$ exit
logout
otto@DESKTOP:~
$ pwd
/home/otto


- falls ein logout-Script existiert, 
          wird es hier abgearbeitet.