Dies sind Musterlösungen zur Shell-Safari. Natürlich mag es für jeden der Beispielfälle noch weitere Lösungen geben als die hier vorgestellten.
Insgesamt hoffe ich, dir hat die Shell-Safari wenigstens ein bisschen Spaß und Inspiration gebracht. ^^
'ls' Listet die Dateien auf, die im aktuellen Verzeichnis liegen. 'pwd' ('present working directory') verrät dir, in welchem Verzeichnis du dich gerade befindest.
mkdir Safari
('make directory') legt ein neues Verzeichnis mit Namen 'Safari' an. Mit 'cd' ('change directory') wechselst du in ein anderes Verzeichnis. Gibt man ein Verzeichnis an, so wechselt man in dieses. Der Aufruf von
cd
ohne Parameter wechselt standardmäßig ins eigene Homeverzeichnis,
cd ..
ins übergeordnete Verzeichnis.
man
Ist der Befehl zum Aufruf des MANuals, dem man als Parameter den entsprechenden Befehl (oder auch eine C++-Methode) übergeben kann.
man ls
ruft folgerichtig die Dokumentation von 'ls' in der Shell auf. Man kann mit den Pfeiltasten navigieren, mit '/<STRING>' die Manpage nach '<STRING>' durchsuchen und mit 'q' die Manpage wieder verlassen.
Der Parameter
funktioniert nicht bei allen Befehlen, aber bei denen, die ihn unterstützen, gibt er eine verkürzte Hilfe aus.
Zusammen erhält man
ls *.pdf # Geier nach Dateinamen sortiert ls -r *.pdf # Geier nach Dateinamen in umgekehrter Reihenfolge sortiert ls -S *.pdf # Geier nach Dateigröße (größter zuerst) ls -Slh *.pdf # S --> wie oben, l --> mehr Infos, h --> human-readable
Die Ausgabe einer vollständigen Dateiliste erfolgt mit
ls -lh *.pdf
Allerdings erhalten wir hier viel mehr Information:
… Eigentlich interessieren wir uns nur für die erste, die dritte, die fünfte und die letzte Spalte.
Man erreicht dies mit folgender Erweiterung fast:
ls -lh *.pdf | cut -d " " -f 1,3,5,9
Ausgabe:
…
… Was passiert hier?
Nun, 'cut' schneidet die 1., 3., 5. und 9. Spalte (Parameter '-f') des Textes, der übergeben wird, aus und wirft alles andere weg. Das Trennzeichen für Spalten wird mit der '-d'-Option als „ “ (Space) festgelegt. Der unscheinbare Strich '|' (die Pipe) leitet allgemein die Standard-Ausgabe des linken Befehls (hier also das ls) an den rechten (hier das cut) weiter. cut bekommt also zunächst die komplette Ausgabe und sucht daraus die genannten Spalten.
Wie bekommen wir es nun hin, dass cut wirklich immer die letzte Spalte (statt der 9.) ausgibt? Nun, nicht ohne weitere Tools. Eine Variante wäre, statt 'cut' 'awk' zu verwenden:
ls -lh *.pdf | awk '{print $1,$3,$5,$NF}'
awk hat allerdings eine recht gewöhnungsbedürftige Syntax. Eine andere Variante wäre
ls -lh *.pdf | sed 's/ / /g' | cut -d " " -f 1,3,5,9
Wir sehen hier, dass man auch mehr als eine Pipe benutzen kann. (Der sed-Befehl wird später noch ausführlicher diskutiert. Hier ersetzt er schlicht zwei Spaces durch eines, bevor dann diese Ausgabe erneut gepiped wird und so zu cut gelangt.
Nun soll das Ganze noch in eine Textdatei. Heißt diese filelist.txt, so hilft einer der beiden Befehle
ls -lh *.pdf | sed 's/ / /g' | cut -d " " -f 1,3,5,9 > filelist.txt ls -lh *.pdf | sed 's/ / /g' | cut -d " " -f 1,3,5,9 >> filelist.txt
Das '>' bzw. '»' sind sogenannte Redirects, mit denen die Ausgabe in eine Textdatei „umgeleitet“ wird, deren Name eben filelist.txt ist.
Der Unterschied zwischen den beiden Varianten besteht darin, dass '>' immer wieder die Zieldatei überschreibt (wenn sie schon existiert), während '»' den jeweiligen Text (hier also die Ausgabe des vorangehenden Befehls) immer am Ende einer eventuell bereits bestehenden Textdatei anhängt.
Kommen wir als nächstes zu 'less' und 'more', die beide „more or less“ das gleiche tun, nämlich das, was sie ausgeben sollen, seitenweise darstellen. Das bedeutet, dass sie überprüfen, wie groß aktuell das Fenster ist, in dem sich die Shell befindet und dann nur die ersten Zeilen ausgibt, die in das Fenster passen. Beide bieten die Möglichkeit, mit '/' (wie bei den Manpages) nach Strings zu suchen und beide bieten die Möglichkeit, durch die gesamte Ausgabe zu navigieren. Und genau darin, mit welchen Tasten die Steuerung geschieht, unterscheiden sich die beiden Tools. Ich persönlich verwende lieber 'less', da die Steuerung der der Manpages ähnlicher ist, aber letztlich ist das Geschmackssache.
more filelist.txt less filelist.txt
würden also die Textdatei filelist.txt „seitenweise“ ausgeben.
Um als nächstes die Dateigröße von Geier 153 auszugeben, kann man nun entweder die Textdatei mit less oder more aufrufen und mit '/' nach 153.pdf suchen, oder aber direkt mit
ls -lh 153.pdf
Alternativ kann man auch die Ausgabe von 'ls -lh' nach less oder more pipen:
ls -lh *.pdf | less
Nun kann man wieder mit '/' suchen.
Natürlich gibt es noch weitere Möglichkeiten. hier ging es primär darum, dass man less (oder more) sowohl per Pipe eine Ausgabe übergeben kann, als auch beide mit nachfolgendem Parameter „Textdatei“ aufrufen kann.
Am Ende noch kurz zu cat, das tatsächlich auch einfache Textdateien ausgeben kann. (Aber noch viel mehr!)
cat filelist.txt # Ausgabe der Textdatei filelist.txt cat filelist.txt | less # Ausgabe der Textdatei filelist.txt mit less (Hier wie 'less filelist.txt') cat filelist.txt | grep "153" # Ausgabe der Textdatei filelist.txt, aber nur der Zeilen, die den String "153" enthalten grep "153" filelist.txt # Hier wie oben
Hier lernen wir den (unglaublich nützlichen) Befehl 'grep' kennen, der Textdateien (ja, mehrere!) nach Strings durchsuchen kann. Man probiere zum Beispiel
cp filelist.txt filelist2.txt grep "153" *.txt
Wir erhalten nun zweimal das altbekannte Ergebnis, aber grep sagt uns auch, in welcher Datei es fündig geworden ist.
Damit können wir die Suche nach der Dateigröße von Geier 153 noch verschönern:
ls -l | grep "153.pdf" | cut -d " " -f 5
Shell-Skripte können grundsätzlich jede Dateiendung haben, aber um sie sofort zu erkennen, bietet es sich an, die Erweiterung '.sh' zu verwenden.
In diesem Beispiel heißt das Shell-Skript 'quekengetter.sh'
Zunächst schauen wir uns die einzelnen Elemente des Skriptes an:
#!/bin/bash
ist die erste Zeile des Skriptes. In ihr wird festgelegt, dass alle weiteren Zeilen mit dem Programm '/bin/bash', also der BourneAgain-SHell, ausgeführt werden sollen. Hier kann natürlich auch eine andere Shell eingetragen werden;
#!/bin/zsh
oder
#!/bin/sh
funktionieren auch, sofern die Shells installiert sind und im Verzeichnis /bin tatsächlich auch liegen. (Hier gibt es zum Teil Unterschiede je nach verwendeter Linux-Distribution. ARCH Linux hat die Shells zum Beispiel zwischenzeitlich in /usr/bin gelegt und für Kompatibilität unter /bin verlinkt.)
Kommen wir nun zu den nächsten Zeilen:
# Dieses Shell-Skript erstellt einen neuen Unterordner, # wechselt in diesen, lädt ein neues Archiv vom Fachschaftsserver # und entpackt dieses
Diese sind einfach Kommentare; zu erkennen an der vorangestellten Raute '#'. Man beachte den Unterschied zur ersten Zeile! dort steht '#!', was die Shell definiert, während '#' Kommentare anzeigt.
Nun folgen die eigentlichen Befehle:
mkdir queken cd queken wget https://www.fsmpi.rwth-aachen.de/pubwiki/images/8/86/Safari_queken.zip unzip Safari_queken.zip
Diese sind bereits bekannt und deswegen erspare ich mir, hierauf dezidiert einzugehen.
Nun bleibt allerdings die Frage: Wie startet man so ein Skript?
Man kann einfach
bash quekengetter.sh zsh quekengetter.sh sh quekengetter.sh ...
am Prompt eingeben, aber dann wäre es relativ sinnlos, im Skript selber spezifiziert zu haben, womit es laufen soll.
Eine schönere Variante (die auch für Binärdateien und eigentlich alles ausführbare funktioniert) ist
./quekengetter.sh
Allerdings muss dafür das Shell-Skript als ausführbar markiert sein, was standardmäßig nicht der Fall ist. Ein 'ls -l' würde wohl etwa folgende Ausgabe haben:
Will man das Skript ausführen, so kann man mit dem Befehl
chmod u+x quekengetter.sh
das Attribut 'eXecutable' hinzufügen:
Nun kann der Besitzer 'owner' (und niemand - außer root - sonst) das Skript mit
./quekengetter.sh
ausführen. Will man auch der zugewiesenen Gruppe 'group' oder allen andern Rechte zum Ausführen des Skriptes einräumen, so erledigt man dies mit
chmod g+x quekengetter.sh chmod o+x quekengetter.sh
Man kann auch kombinieren:
chmod ug+x quekengetter.sh chmod go+x quekengetter.sh chmod ugo+x quekengetter.sh
Hat man das Skript 'lines.sh' angelegt und ausführbar gemacht ('chmod u+x lines.sh'), kann man es aufrufen mit
./lines.sh <(Text-)Datei>
Gehen wir nun genauer die einzelnen Zeilen durch.
#!/bin/bash ## Kennen wir schon. ZEILEN=$(cat $1 | wc -l) ## $(...) sagt, dass '...' in einer Shell ausgeführt werden soll und der Ausdruck die Rückgabe der Shell repräsentieren soll. ## Den Befehl 'cat' kennen wir schon. $1 steht für den **ersten** Parameter, der dem Skript übergeben wurde, ## Beim Aufruf mit './lines.sh <(Text-)Datei>' ist $1 also <(Text-)Datei>. Damit wird der Befehl zu //cat <(Text-)Datei>'// ## Die folgende Pipe ('|') leitet die Ausgabe von cat dann an wc, welches die Zeilen, Leerzeilen und Zeichen in einer ## Textdatei zählt. '-l' weist wc an, nur die Zahl der Zeilen auszugeben. $(...) liefert also die Zahl der Zeilen in ## der übergebenen Datei. ## ZEILEN=... definiert eine neue (oder redefiniert eine bestehende) Variable 'ZEILEN' und weist ihr den Wert ... zu. ## Hier wird also die Zahl der Zeilen der übergebenen Datei bestimmt und in der Variable 'ZEILEN' gespeichert. echo "Die Datei $1 hat ${ZEILEN} Zeilen." ## Dies ist einfach eine Ausgabe. Man kann die Inhalte der Variablen ausgeben mit vorangehendem $-Zeichen. ## Hier muss man allerdings auf den Kontext achten. in einer ""-Ummgebung funktioniert das anders als in //-Umgebungen// ## oder wenn man den String gar nicht mit Anführungszeichen klammert. (s. oben) ## Zu diesem Thema (Escaping) folgt unten noch mehr.
Kommen wir nun zum Shell-Skript, das nach den Rechten schaut. Für die erste Teilaufgabe ist eine mögliche Lösung
#!/bin/bash ls -1l | sed '1d' | grep "\-$1" | awk '{print $NF}' > dummy.foo ## sed '1d' truncates first line if [ "$(cat dummy.foo | wc -l)" == 0 ] then echo "No files matching pattern found." else cat dummy.foo | grep -v "dummy.foo" fi if [ -e dummy.foo ] then rm dummy.foo fi
Das ist noch recht einfach und basiert nur auf bekanntem Gedöhns deswegen kommentiere ich das jetzt nicht erheblich weiter. Man achte allerdings auf den Backslash vor dem '-' in Zeile
ls -1l | sed '1d' | grep "\-$1" | awk '{print $NF}' > dummy.foo
Er deutet an, dass das folgende Zeichen „escaped“ werden soll, was im Wesentlichen bedeutet, dass das nächste Zeichen normaler Weise alleine als irgendein Sonder- oder Steuerzeichen interpretiert würde, und das hier eben nicht geschehen soll.
Escaping funktioniert je nach Kontext ein wenig unterschiedlich und ist eine beliebte Fehlerquelle. Es würde hier zu weit führen, sie hier en detail zu erklären. Am einfachsten ist es, wenn man hier „learning-by-doing“ betreibt, aber dafür muss man eben wissen dass es dieses Escaping gibt.
Des Weiteren sollte es zwischenzeitlich möglich sein, die Funktionalität der anderen Zeilen selber zu ermitteln oder auch einfach zu verstehen. ^^
Zu Task 2:
#!/bin/bash if [ "$2" == "" ] || [ "$2" == "u" ] then echo "Searching for owner permissions." GREPSTRING="\-$1" elif [ "$2" == "g" ] then echo "Identifier for group permissions found." GREPSTRING="\-...$1" ## Dots are jokers for any char. elif [ "$2" == "o" ] then echo "Identifier for others' permissions found." GREPSTRING="\-......$1" else echo "Wrong parameter passed. Using default" GREPSTRING="\-$1" fi ls -1l | sed '1d' | grep "${GREPSTRING}" | awk '{print $NF}' > dummy.foo if [ "$(cat dummy.foo | wc -l)" == 0 ] then echo "No files matching pattern found." else cat dummy.foo | grep -v "dummy.foo" fi if [ -e dummy.foo ] then rm dummy.foo fi
Task 3:
#!/bin/bash if [ "$3" == "not" ] then GREPCMD="grep -v" else GREPCMD="grep" fi if [ "$2" == "" ] || [ "$2" == "u" ] then echo "Searching for owner permissions." GREPSTRING="\-$1" elif [ "$2" == "g" ] then echo "Identifier for group permissions found." GREPSTRING="\-...$1" elif [ "$2" == "o" ] then echo "Identifier for others' permissions found." GREPSTRING="\-......$1" else echo "Wrong parameter passed. Using default" GREPSTRING="\-$1" fi ls -1l | sed '1d' | ${GREPCMD} "${GREPSTRING}" | awk '{print $NF}' > dummy.foo ## You can also use $VARIABLE for commands if [ "$(cat dummy.foo | wc -l)" == 0 ] then echo "No files matching pattern found." else cat dummy.foo | grep -v "dummy.foo" fi if [ -e dummy.foo ] then rm dummy.foo fi
convert -delay 10 baby{0..26}.png animated.gif for f in *.png do convert -density 300 -units PixelsPerInch ${f} ${f%.png}.eps done for f in *.eps ; do epstopdf ${f} ; done pdftk baby{0..26}.pdf cat output combined.pdf
Da diese Aufgabe weitgehend Recherche ist, beschränke ich mich nur auf den letzten Punkt.
publicip(){ wget http://checkip.dyndns.com/ -O - 2>/dev/null | grep "Current IP Address" | cut -d ":" -f 2- | sed 's#</body></html>##g' | sed 's/ //' }
definiert die Shell-Methode 'publicip', die nach Definition mit einem simplen
publicip
in der Shell aufgerufen werden kann.
Im deinem Homeverzeichnis liegt eine (versteckte) Datei .bashrc (bzw. .zshrc, etc.) in die Aliase, Shell-Voids, Variablen und so weiter geschrieben werden können. Bei jedem Aufruf der <SHELL> wird die .<SHELL>rc ausgeführt, sodass alles, was in dieser Datei drinne steht, standardmäßig in der Shell zur Verfügung stehen.
Das Programm 'intro.cpp' kann zum Beispiel mit dem Compiler 'g++' compilliert werden:
g++ intro.cpp
Dies liefert ein ausführbares Programm mit dem Namen 'a.out', welches mit
./a.out
gestartet werden kann.
Optional kann man auch
g++ intro.cpp -o <OUTPUTFILE>
zum compillieren verwenden, was als Ausgabe statt der 'a.out' eine Datei mit Namen '<OUTPUTFILE>' zur Folge hat und mit
./<OUTPUTFILE>
aufzurufen ist.
Dieses Programm gibt im Wesentlichen zwei Textzeilen aus, aber während das eine eine „normale“ Ausgabe ist, ist das andere ein „Error“.
Um nun beide Ausgaben in eine Datei zu leiten, ist ein simples '>' (redirect) nicht ausreichend. Dieses leitet nur die Standardausgabe weiter.
Das impliziert bereits, dass es noch eine andere als die Standardausgabe 'stdout' gibt, nämlich 'Standard Error' ('stderr').
Um beide Ausgaben zu pipen, kann man '&>' benutzen:
./<OUTPUTFILE> &> output.txt.
Für weitere Kombinationen von Umleitungen kann man sich auf die Website http://www.thomas-krenn.com/de/wiki/Bash_sdtout_und_stderr_umleiten begeben.
Das Programm 'sequenziell.cpp' compilliert man analog mit
g++ sequenziell.cpp -o sequenziell
Der Aufruf erfolgt mit
./sequenziell .5 100000000
Dieses Programm ist primär dazu da um zu zeigen, wie man an ein selbstgeschriebenes Programm Parameter übergeben kann. Und Überraschung! Das geht wie bei Shell-Skripten. ^^
Das Programm 'parallel.cpp' benutzt die freie Parralelisierungsbibliothek OpenMPI, die zunächst auf dem System installiert sein muss. Falls nicht, kannst du es mit
sudo apt-get install gcc versuchen.
Das reicht aber noch nicht aus. Wenn man versucht, einfach mit
g++ parallel.cpp -o parallel
zu compillieren, erhält man (etwa) folgenden Fehler:
/tmp/ccSwDJju.o: In Funktion `main': parallel.cpp:(.text+0x42): Nicht definierter Verweis auf `omp_get_max_threads' parallel.cpp:(.text+0xd0): Nicht definierter Verweis auf `omp_get_max_threads' parallel.cpp:(.text+0x13e): Nicht definierter Verweis auf `omp_get_thread_num' parallel.cpp:(.text+0x198): Nicht definierter Verweis auf `omp_get_max_threads' collect2: error: ld returned 1 exit status
Das Problem ist, dass der Compiler nicht weiß, was er mit dieser Zeile tun soll:
#include <omp.h>
Hier würden die omp-Methoden deklariert und die entsprechende Datei wird schlicht nicht gefunden.
Dies ändert man mit dem Compilerflag '-fopenmp':
g++ -fopenmp parallel.cpp -o parallel
Nun kann man mit
./parallel .5 100000000
das Programm aufrufen.
Wenn man nun ein zweites Terminal startet kann man zum Beispiel mit 'htop' (sudo apt-get install htop) überprüfen, wie die Auslastung der eigenen Kerne ist.
Die Laufzeiten verrät einem der Befehl 'time':
time ./parallel .5 100000000
bzw.: time ./sequenziell .5 100000000