Hier ein paar Tipps, Tricks und Tools für die tägliche Arbeit auf der Shell, die zu klein sind für einen eigenen Artikel.
Unix-Timestamp lesen
Ab und an wird man mit einem Unix-Timestamp konfrontiert und will wissen, welches Datum und/oder welche Zeit dahinter steckt. So geht’s:
$ date -d '1970-01-01 1254824106 sec' -u Di 6. Okt 10:26:10 UTC 2009
Perl kann das natürlich auch:
$ perl -e 'print scalar localtime(1254824106)," "' Tue Oct 6 12:15:06 2009
Perl gibt die Zeit in der lokalen Zeitzone aus (hier CEST), bei date müssen wir mit Hilfe von -u UTC einstellen, sonst stimmt die Ausgabe u.U. nicht.
Einen Unix-Timestamp erzeugt man so:
$ date -d '2009-02-22 12:15:06' '+%s'
Den Feldseparator $IFS auf Newline umstellen
Um beispielsweise in einer For-In-Schleife eine Datei mit Strings oder eine Variable mit mehreren Zeilen zu verarbeiten, muss der interne Feldseparator IFS auf einen Zeilenumburch umgestellt werden. Ein einfaches IFS=“
“ funktioniert aber nicht. Hier der Trick:
# Tabulator- und Newline-Zeichen als Variable, ist so leichter lesbar Tab=$' ' Newline=$' ' IFS=$Newline for LINE in `echo -e $OUTPUT` do echo '['`date +%s`']' "${LOGPREFIX}$LINE" >>$LOGFILE done IFS=
Zeilenumbruch in Ausgaben behalten
Hat man die Ausgabe eines Befehls in eine Shell-Variable übernommen und will diese wieder mit echo ausgeben oder weiterverarbeiten, so muss man die Variable in Anführungszeichen setzen. Sonst besteht die Ausgabe nur aus einer einzelnen Zeile.
OUT=`cat /etc/passwd` # alles eine Zeile: echo $OUT echo $OUT | wc -l # so sieht es besser aus: echo "$OUT" echo "$OUT" | wc -l
FOR-Schleife in der Shell
Die Bourne- (sh) und Bourne-Again-Shell (bash) kennen nicht nur die For-In-Schleife, die ja gerne mit Dateien oder Listen zur Anwendung kommt, sondern auch die ganz normale For-Schleife mit Startwert, While-Bedingung und Inkrement-Statement:
for ((x=1; x <= 10; x += 2)) do commands done
Damit lassen sich z.B. eine Reihe von IP-Adressen pingen:
for ((x=33; x <= 60; x += 1));do ping 10.239.16.$x -n 1 -m 1;done
Wenn die Shell Brache-Expansion beherrscht, geht das auch einfacher:
for x in {33..60};do ping 10.239.16.$x -n 1 -m 1;done
Die Dateien eines Verzeichnisses mit vollen Pfad auflisten
Manchmal wünscht man sich für ls
einen Schalter, mit dem man auch den gesamten Pfad zur Datei anzeigen lassen kann, weil man die Datei(en) per cut&paste irgendwohin kopieren muss. ls
kann das nicht, aber mit find
geht es:
find . -maxdepth 1 -printf "$PWD/%P " | egrep -v "/$"
Ich habe mit das praktischer Weise als Alias abgelegt:
alias lsp='find . -maxdepth 1 -printf "$PWD/%P " | egrep -v "/$"'
Port und Protokoll aus einem Firewall-Log extrahieren und in /etc/services nachschlagen
Um aus den (komprimierten) Logs den Zielport und das Protokoll zu holen und das Ergebnis in eine Datei zu schreiben dient der erste Einzeiler:
zgrep Analoge firewall-2009* | perl -n -e 'print "$2/$1 " if /PROTO=(w+)s.+DPT=(d+)/' \n| sort | uniq > /tmp/analoge_einwahl.txt
Danach wird die Datei mit dem folgenden Schnipsel gelesen und der jeweils passende Eintrag in der /etc/services gesucht:
for TEXT in `cat /tmp/analoge_einwahl.txt`; do echo -n "$TEXT : "; grep -i " $TEXT " /etc/services; done
ls -l nach Größe sortiert
POSIX‘ ls
hat leider keinen Schalter, um nach Größe der Dateien zu sortieren, hier muss man sort
bemühen:
ls -l | sort -n -b -k 5,5
Dabei muss die Größenangabe die fünfte Spalte sein.
Relative Datumsangaben
date
versteht auch relative Zeitangaben, z.B. yesterday. Und man kann ihm ein Datum vorgeben, auf das sich die relative Berechnung beziehen soll:
date --date="2009-12-09 +91 days"
Siehe auch info date
und dort „Date input formats:: Specifying date strings“.
Shell-Sitzungen komplett aufzeichnen
In der Shell kann man sich zwar alle Eingaben mit Hilfe von history
anzeigen lassen, aber die Ausgaben der Befehle bleiben hier natürlich außen vor. Hier hilft script
. Der Aufruf erfolgt über
script -a logdatei
Alles, was man von nun an eingibt landet inklusive Ausgabe in der Datei logdatei.script
. Mehr auf Linux-Comunity.
Rechnen mit Kommazahlen und mathematische Funktionen in der Shell
Von Haus aus kann die Shell nur mit Integerzahlen einfache Rechenoperationen ausführen. Will man mit Kommazaheln rechnen oder benötigt komplexere Funktionen wie z.B. Sinus, so muss man bc
zu Hilfe nehmen:
NEEDGB=$(echo "scale=2 ; $NEEDED/1073741824" | bc)
Eine Anleitung, wie man bc benutzt, findet sich unter bc – Rechnen mit Kommazahlen und mathematische Funktionen auf den Linuxseiten von Klaus Gerhardt.
Letzte Zeile einer Datei entfernen
sed -n '$!p' filename>temp mv temp filename
Der Schalter -n
bewirkt, dass nur Zeilen mit den p
Kommando ausgegeben werden. $
ist das Symbol für die letzte Zeile, das !
negiert die Auswahl. sed gibt also alle Zeile außer der Letzten aus.
Erste Zeile einer Datei entfernen
Entsprechend zu dem oben beschriebenen kann man natürlich auch die erste Zeile entfernen, in dem man diese Zeile adressiert:
sed -n '1!p' filename>temp mv temp filenam
Geht aber noch einfacher:
sed 1,1d filename>temp
Oder sed 1,2d
, wenn die ersten beiden Zeilen entfernt werden sollen.
Teil einer Datei ab/bis zu einer bestimmten Kennung ausgeben
Machmal möchte man eine Textdatei (z.B. eine Logdatei) erst ab oder nur bis zu einer bestimmten Stelle ausgeben, man weiß aber die Zeilennummer nicht, so dass head und tail nicht benutzt werden können. Hier hilft der Bereichsoperator von Perl.
Datei ab dem ersten Juni bis zum Ende ausgeben:
perl -n -e 'print $_ if /Jun 1 .+ 2010/ .. eof();' logdatei.log
Datei vom Anfang bis zum ersten Juni ausgeben:
perl -n -e 'print $_ if 1 .. /Jun 1 .+ 2010/;' logdatei.log
Hier wird dann leider die Zeile mit der Ende-Bedingung mit ausgegeben, aber das kann man ja wie oben bei Letzte Zeile einer Datei entfernen behandeln.
Anhang einer Mail auf der Kommandozeile extrahieren
Anhänge sind in E-Mails üblicher Weise base64-codiert gespeichert. Will man diesen Anhang extrahieren, so muss man zunächst nur diesen kodierten Teil aus der Mail herauslösen (z.B. s.o.) und ihn dann decodieren:
base64 -d < data-b64 > 2011-02-02.sql.gz
mehr dazu auch unter existence trainer: Base64 decode to file.
Prüfen, ob alle Zeilen in der einen Datei auch in der anderen vorhanden sind
Der Fall: Ich hatte eine Liste von Usern und musste nun feststellen, ob diese alle auf einem System eingerichtet sind. Dazu wurden erst einmal die Usernamen aus der /etc/passwd
extrahiert und sortiert:
cut -d':' -f1 /etc/passwd | sort > all-users
Die Liste mit den fraglichen Usern wurde nun auch sortiert und Leerzeilen entfernt:
sort puk-user | grep -v '^$' >puk-user.sort mv puk-user.sort puk-user
Danach wurden die beiden Dateien mit Hilfe von join
zusammengeführt, wobei man sich durch den Parameter -v 2
nur die unpassenden Zeilen aus der zweiten Datei anzeigen lässt (die also in der Ersten fehlen):
join -v 2 all-users puk-user
Nun weiß ich, welche User ich noch einrichten muss.
Aus eine Spalte eine Komma-separierte Liste machen
Hat man eine Datei, die in jeder Zeile einen Wert o.ä. enthält (z.B. Usernamen) und möchte man daraus eine durch Kommas getrennte Aufzählung machen, geht das sehr einfach mit paste:
paste -s -d"," puk-user | sed 's/,/, /g'
Man kann so sogar aus jeweils zwei Zeilen eine machen:
paste -s -d" " datei
Dies ist z.B. bei der Logdatei des snmptrapd recht nützlich:
paste -s -d" " net-snmpd.log
Ping auf mehrere Adressen
Ist nmap nicht installiert, kann man auch mit einer For-Schleife arbeiten (siehe auch oben unter FOR-Schleife in der Shell).
Bei SunOS/Solaris geht es z.B. so:
for N in {1..254}; do ping 172.27.47.$N 3; done
Paralleles Ausführen mit Timeout
Ich brauchte in einem Shell-Script die Möglichkeit, für eine bestimmte Zeit eine Meldung anzuzeigen, während eine Anwendung gestartet wurde. Bei cateee wurde ich fündig:
# run command1 in background, and the sleeping killer command1 & pid=$! ( sleep 60; kill $pid ) & # other stuffs command2 # wait command1 wait $pid
Ausgabe sortieren, aber die Kopfzeile oben lassen
Bei climagic wurde gezeigt, wie man mit Hilfe von AWK die Ausgabe von df
nach der prozentualen Belegung sortiert, aber die Kopfzeile schön beibehält:
df -hP | awk 'NR==1;NR>1{print|"sort -k5rn"}'
Das funktioniert natürlich auch mit anderen Befehlen.
Mail von Programm: Habe fertig!
Man hat ein Programm gestartet, und möchte nun aber eine Mail bekommen, wenn es fertig ist. So geht’s:
bg ; wait %1 ; echo "done" | mail -s "done" you@example.com
Gefunden bei climagic
Funktion, um das Datum einer Datei an den Dateinamen anzuhängen
mv2date () { mv $1 $1.`stat -c '%y' $1 | sed 's/s.*$//'` ; }
Aufruf: mv2date Datei
Kann man in die .bash_profile aufnehmen, damit es immer zur Verfügung steht.
Dateien bis auf jeweils die erste eines Monats löschen
Macht man beispielsweise wöchentlich Backups, so möchte mal vielleicht irgendwann die alten Jahre löschen, aber halt nicht alle, von jedem Monat soll das erste erhalten bleiben.
YEAR=2017 for n in {01..12}; do ls -1 ${YEAR}-$n* | sort | tail -n +2 | xargs rm -fv; done
Zeilen einer Datei, die in der andere Datei fehlen
Will man zwei Dateien vergleichen, aber als Ergebnis nur die Zeilen erhalten, die nur in der ersten Datei vorhanden sind, benutzt man besser comm
statt diff
:
comm -23 a.txt b.txt
Siehe auch https://stackoverflow.com/a/14473092 (aber auch https://stackoverflow.com/a/18205289)
HP-UX: Alle Prozesse eines Users killen
Unter HP-UX kann man killall
nicht zum Beenden von Prozessen eines bestimmten Users benutzen, pkill
gibt es nicht. Daher muss man sich so behelfen:
ps -u name-des-users | awk 'NR>1{print $1}' | xargs -t -i kill -9 {}
Man kann den xarg-Parameter -t durch -p ersetzen, dann wird immer vorher gefragt.
STDOUT und STDERR in verschiedenen Variablen speichern
Ja, das geht, ist aber etwas komplex. Bei Stackoverflow findet sich unter Punkt 7. der folgenden Antwort eine gute Lösung: https://stackoverflow.com/a/59592881/3112832