Shell Tipps & Tricks

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

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.