Sonntag, 12. Februar 2012

Den bash Prompt den eigenen Bedürfnissen anpassen

Wegen der im vorgängigen Post erwähnten Anpassung an der /etc/hosts schaute mein Bash Shell Prompt plötzlich folgendermassen aus:

mario@192:/ $

Ich habe mich deshalb daran gemacht, das Prompt meinen eigenen Bedürfnissen anzupassen. Folgendermassen schaut es nun aus:

[mario@192.168.0.102:/] $ 

Die Ausgabe beim root-User sieht identisch aus, ausser dass das $ durch ein # ersetzt wurde und der Prompt in roter Farbe hervorgehoben ist.

Für die Einstellungen des root-Users habe ich /etc/bash.bashrc angepasst:

...#PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
PS1="\\[$(tput setaf 1)\\]\\u@\\H:\\w # \\[$(tput sgr0)\\]"

Für die Einstellungen aller anderen User muss man sich in die /etc/profiles bemühen:

# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).

if [ "`id -u`" -eq 0 ]; then
  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11"
else
  PATH="/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games"
fi

if [ "$PS1" ]; then
  if [ "$BASH" ]; then
    #PS1='\u@\h:\w\$ '
    PS1="[\\u@\\H:\\w] $ "
  else
    if [ "`id -u`" -eq 0 ]; then
      #PS1='# '
      PS1="[\\u@\\H:\\w] # "
    else
      #PS1='$ '
      PS1="[\\u@\\H:\\w] $ "
    fi
  fi
fi

export PATH

umask 022

Damit dies bei den einzelnen Benutzern auch wirklich so ausschaut, müssen diese in ~/.bashrc und ~/.bash_profile jegliche Definition der PS1-Variable verhindern.

Fazit: Ein riesiges Chaos mit unzähligen Overrides und verschachtelten Abhängigkeiten, welches eines Linux-Betriebssystems im Grunde unwürdig ist.

Tags: ,
Labels: Linux

Keine Kommentare | neuen Kommentar verfassen

Sonntag, 12. Februar 2012

Apache kann den FQDN des Servers nicht festlegen

Seit Jahren ärgere ich mich damit herum, dass meine Cron-Logs jedes Wochenende mit folgendem Fehler aufwarten:

apache2: Could not reliably determine the server's fully qualified domain name, using 192.168.0.101 for ServerName

Diese Meldung taucht auch auf, wenn ich den Server mit apache2ctl graceful respektive apache2ctl restart neu starte.

Heute nun habe ich mich endlich des Problems angenommen und teile die Lösung gerne mit der ganzen Online-Welt.

Der vom lokalen BIND (DNS-Server) geliefert Hostname meines Web-Servers lautete wie folgt:

$ host 192.168.0.101
101.0.168.192.in-addr.arpa domain name pointer alpha.emeidi.local.

In meiner /etc/hosts stand aber nur folgendes:

127.0.0.1	localhost

192.168.0.101	ALPHA
...

Nach einigem Googlen wechselte ich die Zeile beginnend mit 192.168.0.101 folgendermassen aus:

192.168.0.101	ALPHA ALPHA.emeidi.local mad4you.homeip.net

Und siehe da, ab sofort kann Apache den Fully Qualified Domain Name des Servers selber erraten.

Tags: , ,
Labels: Linux

Keine Kommentare | neuen Kommentar verfassen

Samstag, 11. Februar 2012

curlftps mit Zeichensatz-Problemen

Ich ziehe mir wöchentlich ein Backup eines produktiven Web-Servers auf meinen Testserver. Da Endbenutzer Dateien auf die Web-Site laden und Ordner erstellen können, gibt es dort mittlerweile auch Dateinamen mit Sonderzeichen wie äöü.

Die Kombination aus curlftps (zur Bereistellung eines FTP-Mounts welcher wie eine lokale Festplatte angesprochen werden kann, zu Gunsten von rsync, mit welchem ich nur neu hinzugekommene Dateien übertrage) hat nun aber zu Problemen bei der Darstellung der Dateien mit Sonderzeichen geführt. Anstelle des ä bei „Bräteln“ sah ich auf der Kommandozeile mit ls nur „Br?teln“.

Seit ich curlftps mit folgenden Optionen aufrufe, klappt es auch mit der korrekten Übertragung der Dateinamen mit Sonderzeichen:

/usr/bin/curlftpfs -o codepage=latin1 -o iocharset=utf8 -r -s 'server.tld' '/mnt/ftpBackup/server.tld'

Damit wird tarsnap hoffentlich auch nicht mehr mit folgenden Fehlermeldungen auffallen:

...
tarsnap: var/www/sites/server.tld/Bilder/T�ftelwettbewerb/: Can't translate pathname 'var/www/sites/server.tld/Bilder/T�ftelwettbewerb/' to UTF-8
...

Übrigens: Meine locale-Einstellungen schauen folgender massen aus:

LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

Tags: ,
Labels: Linux

Keine Kommentare | neuen Kommentar verfassen

Samstag, 11. Februar 2012

Vom Nutzen einer PHP Debug-Klasse

Seit mehreren Jahren verwende ich eine Sammlung von selber entwickelten emeidi*-PHP-Klassen, um Web-Sites und -Applikationen zu entwickeln. Leider hege ich immer noch grosse Skrupel, diese Klassen via Github als Open Source Software freizugeben — einerseits weil sich im Quellcode doch der eine oder andere unschöne Bock findet, andererseits, weil die Klassen Sicherheitslücken enthalten könnten, die bei produktiven Web-Sites ausgenützt werden könnten. Hinzu kommt mein Amazon-API-Key, den ich kurzerhand in die emeidiAmazon-Klasse festkodiert habe …

Unverzichtbares Kernstück ist dabei meine emeidiDebug-Klasse, welche für die professionelle, effiziente Web-Entwicklung unabdingbar ist. Jede andere meiner Klasse instanziert beim Laden die emeidiDebug-Klasse, mit welcher ich im ganzen Programmcode nützliche Fehlermeldungen und/oder Informationen zum Programmablauf festhalte.

Auf produktiven Web-Sites gebe ich den Inhalt des Debug-Objektes natürlich nicht aus, aber auf dem Entwicklungsserver ist es eine grosse Unterstützung, wenn ich am Ende der ausgegebenen Web-Seite noch folgenden Code hinpflanze:

...
<?php print $klasse->debug->getHtml(); ?>
</body>
</html>

Im ganzen PHP-Code finden sich zur Befüllung dieses Objekts Konstrukte wie

$this->debug->add('Not dumping descriptions because length new ' . $lenNew . ' is smaller than lenght old ' . $lenOld,'error');

Teilweise gebe ich auf produktiven Web-Sites wichtige Warnmeldungen auch in das PHP Error-Log aus. Während ich das früher direkt mit error_log(); gemacht habe, benutze ich heute ebenfalls meine Debug-Klasse dafür.

Der Grund dafür ist simpel: Nicht nur möchte ich das gesamte Debugging über meine Debug-Klasse laufen lassen, nein, meine Debug-Klasse ist auch geschwätziger und hilft mir im Falle meines Testservers, das fehlerhafte Script zu lokalisieren.

Vor einigen Tagen fand ich auf meinem Testserver folgende Einträge in der php.err-Datei:

[06-Feb-2012 00:07:43 UTC] Not dumping descriptions because length new 1778 is smaller than lenght old 1780

Mittlerweile habe ich das ausgebende Script (es wird täglich per Cron-Job aufgerufen) lokalisiert und die Programmierung angepasst. Alt hiess es:

error_log('Not dumping descriptions because length new ' . $lenNew . ' is smaller than lenght old ' . $lenOld);

Neu heisst es:

$this->debug->add('Not dumping descriptions because length new ' . $lenNew . ' is smaller than lenght old ' . $lenOld,'error',true);

Der letzte der Funktion übergebene Parameter true weist meine Debug-Klasse an, einen Eintrag in die php.err-Datei zu machen. Dieser schaut folgendermassen aus:

[11-Feb-2012 16:57:16 UTC] Not dumping descriptions because length new 1778 is smaller than lenght old 1780 in /var/www/apps/weather2ics/weather2ics.class.php on line 95 for URI </bern>

Anhand dieser Informationen kann ich nicht nur die Datei und die verantwortliche Zeile auf einen Blick lokalisieren, welche den Fehler ausgibt, sondern sehe auch noch gerade die URI, mit welcher die Web-App aufgerufen wurde.

Tags: , ,
Labels: Web

1 Kommentar | neuen Kommentar verfassen

Donnerstag, 9. Februar 2012

SAP Spreadsheet-Exporte parsen

… ist leider nicht so einfach.

SAP bietet die Möglichkeit, Reports zu exportieren. Zur Auswahl steht unter anderem auch ein „Spreadsheet“-Format, für welches die Endung .xls vorgeschlagen wird. Dabei handelt es sich aber nicht etwa um das Excel-Binärformat, welches heute durch OOXML abgelöst wurde, sondern um das ältere XLL-Format, was auch immer das genau sein soll. Betrachtet man die aus SAP generierte Datei in einem Text-Editor, ist offensichtlich, dass es sich hierbei nicht um ein Binär- sondern um ein Plaintext-Format mit Tabulator getrennten Spalten handelt.

Wenn diese Datei aber mit Python eingelesen wird und verarbeitet werden soll, ergeben sich Probleme. Die Zeichen sind auf der cygwin-Kommandozeile von einem Leerzeichen getrennt. Es handelt sich also nicht um eine ASCII-Ausgabe.

Ein Blick mit Notepad++ auf die Datei zeigt, dass die Encodierung der Datei UCS-2 Little Endian sein soll. Auf Stackoverflow ist erwähnt, dass diese Encodierung in Python wie UTF-16 behandelt werden kann, und zwar folgendermassen:

file = codecs.open(filenameInput,'Ur',encoding='utf-16be')

Leider führt das aber nicht zum Erfolg, was nachfolgende Fehlermeldung einem schmerzlich vor Augen führt:

Traceback (most recent call last):
  File "./<script>.py", line 140, in 
    data = file.read()
  File "C:\Python27\lib\codecs.py", line 671, in read
    return self.reader.read(size)
  File "C:\Python27\lib\codecs.py", line 477, in read
    newchars, decodedbytes = self.decode(data, self.errors)
UnicodeDecodeError: 'utf16' codec can't decode bytes in position 1082-1083: illegal encoding

Tags: , ,
Labels: Allgemein

Keine Kommentare | neuen Kommentar verfassen

Donnerstag, 9. Februar 2012

Mit openpyxl .xlsx-Dateien ausgeben

Mit dem Python-Modul openpyxl ist es möglich, Excel-Dateien im Microsoft OOXML-Format (.xlsx) zu lesen und zu schreiben.

Da das Erstellen einer solchen Datei im Netz verständlich erklärt ist und hier nicht näher beleuchtet werden soll, ist das Styling von Tabellenzeilen leider kaum dokumentiert.

Hier einige Ansatzpunkte in Form von Code-Schnipseln aus einem meiner Python-Scripts:

from openpyxl.reader.excel import load_workbook
from openpyxl.writer.excel import ExcelWriter

from openpyxl.workbook import Workbook
from openpyxl.worksheet import ColumnDimension
from openpyxl.cell import get_column_letter, column_index_from_string
from openpyxl.style import Color, Fill

# Eigenschaften von Zelle in Reihe 4 und Spalte 3
cellCoord = get_column_letter(3) + "4"

# Fettschrift
sheetResult.cell(cellCoord).style.font.bold = True

# Textausrichtung
sheetResult.cell(cellCoord).style.alignment.horizontal = 'left'
sheetResult.cell(cellCoord).style.alignment.vertical = 'top'

# Textumbruch
sheetResult.cell(cellCoord).style.alignment.wrap_text = True

# Zellfüllung
sheetResult.cell(cellCoord).style.fill.fill_type = Fill.FILL_SOLID 
sheetResult.cell(cellCoord).style.fill.start_color.index = Color.GREEN

# Für Zellfüllung verfügbare Farben
BLACK = 'FF000000'
WHITE = 'FFFFFFFF'
RED = 'FFFF0000'
DARKRED = 'FF800000'
BLUE = 'FF0000FF'
DARKBLUE = 'FF000080'
GREEN = 'FF00FF00'
DARKGREEN = 'FF008000'
YELLOW = 'FFFFFF00'
DARKYELLOW = 'FF808000'

Tags: ,
Labels: Allgemein

Keine Kommentare | neuen Kommentar verfassen

Donnerstag, 9. Februar 2012

Python-Scripts unter Windows in ausführbare .exe-Dateien umwandeln

Mit Python-Scripts kann man auch in einer Grossfirma wie meinem Arbeitgeber Arbeitsabläufe automatisieren, so beispielsweise das parsen einer Excel-Datei mit dem Modul openpyxl. Ich suche in der Excel-Datei in einer bestimmten Spalte nach Parametern und in derselben Zeile dann nach den auf dem produktiven System eingestellten Werten.

Doch was macht man nun mit dem Python-Script, wenn es als stabil empfunden und zur Distribution unter den Arbeitskollegen freigegeben wurde? Man kann nicht erwarten, dass nur ein einziger Mitarbeiter Python auf seinem Windows-Rechner installiert hat und das Script so ausführen kann.

Zum Glück gibt es eine einfache Möglichkeit, um Python-Scripts in eine .exe-Datei umzuwandeln, welche ausschliesslich aus dieser Datei bestehen und deshalb problemlos herumgereicht werden können.

Hierfür benötigt man den pyinstaller (kompatibel mit Python 2.7), welchen man folgendermassen aufruft:

python C:\Source\pyinstaller-1.5.1\pyinstaller.py C:\Source\emeidi\<script>.py -F

Der Switch -F sagt pyinstaller, dass er das Script in eine einzige, grosse .exe-Datei umwandeln und auf den sonst ebenfalls nötigen .dll-Plunder verzichtet.

Anschliessend findet sich die .exe-Datei unter C:\Source\pyinstaller-1.5.1\<script>\dist\<script>.exe, welche man beispielsweise auf den Desktop kopieren und von dort ausführen kann.

Tags: , , ,
Labels: Allgemein

1 Kommentar | neuen Kommentar verfassen

Donnerstag, 9. Februar 2012

Microsoft Word nervt mit der ständigen Frage „Open as read-only?“

Wenn Microsoft Word beim Öffnen einer bestimmten Word-Datei partout immer wieder mit der Frage „<dateiname> should be opened as read-only unless changes to it need to be saved. Open as read-only? “ nervt, muss die Datei neu gespeichert werden.

Hierbei ist aber zu beachten, dass man den Schreibschutz der neu zu erstellenden Datei folgendermassen entfernt:

  1. Microsoft Office Button
  2. Save as
  3. Tools
  4. General Options
  5. [ ] Read-only recommended

Wird die Datei so neu abgelegt, erscheint das nervende Dialogfenster beim Öffnen der Datei nicht mehr.

Tags:
Labels: Allgemein

Keine Kommentare | neuen Kommentar verfassen

Sonntag, 22. Januar 2012

Eine Dropbox Gallery automatisiert herunterladen

Vor kurzem erhielt ich den Link auf die Bilder einer Weihnachtsfeier des vergangenen Jahres. Der Photograph benutzte dabei die Funktion von Dropbox, einen auf den Fileshare-Dienst geladenen Bilderordner über einen Link als Galerie zu veröffentlichen.

Da es mir zu blöd war, die hochauflösenden Bilder von Hand herunterzuladen, machte ich mich auf die Suche nach einem entsprechenden Script. Nach einigem Suchen wurde ich in einem Forumsartikel auf Dropbox selber fündig. Der in den Posts ursprünglich genannte Link auf eine Wiki-Seite von Dropbox funktionierte zwar nicht mehr, aber eine gute Seele hatte sich mittlerweile erbarmt und den Quellcode des Bash-Scriptes in einen Post geladen:

#!/bin/bash
#
# Call this with the URL of a public dropbox photo gallery as the first
# argument, a destination directory as the second argument, and optionally
# give a size (thumbnail|large|extralarge|original) for the third argument
# (defaults to original)
#
# Initial version written 1st June 2010, by Andrew Scheller
#
# Added quoting (so it works if urls or paths contain spaces) on 10th July 2010
#
# Tweaked to use either wget or curl, 17th January 2011
# Updated to current encoding of URLS, 11th October 2011 (python needed)
#

# get binary locations
WGET=$(which wget)
CURL=$(which curl)
if [ -z "$WGET" ] && [ -z "$CURL" ]
then
echo "Please install either wget or curl" >&2
exit 1
fi

if [ -z "$1" ]
then
echo "Required argument (gallery URL) not given"
exit 1
fi
GALLERY_URL=$1
if [ -z "$2" ]
then
echo "Required argument (destination dir) not given"
exit 1
else
if [ ! -d "$2" ]
then
echo "Destination dir '$2' doesn't exist"
exit 1
fi
fi
DESTDIR=$2
SIZE=original
if [ ! -z "$3" ]
then
if [[ "$3" == "thumbnail" || "$3" == "large" || "$3" == "extralarge" || "$3" == "original" ]]
then
SIZE=$3
else
echo "Size must be one of (thumbnail|large|extralarge|original)"
exit 1
fi
fi

TEMP=$(mktemp)
if [ "$WGET" ]
then
"$WGET" -q "$GALLERY_URL" -O "$TEMP"
elif [ "$CURL" ]
then
"$CURL" "$GALLERY_URL" -o "$TEMP" -s
fi
if [ $? -ne 0 ]
then
echo "Something went wrong! Couldn't download the gallery index page"
rm "$TEMP"
exit 1
fi
IFS='
'
FILENAMES=( $(grep "'filename': " "$TEMP" | cut -d"'" -f4) )
IMAGE_URLS=( $(grep "'$SIZE': " "$TEMP" | cut -d"'" -f4) )
rm "$TEMP"
TOTAL_FILENAMES=${#FILENAMES[@]}
TOTAL_IMAGES=${#IMAGE_URLS[@]}
if [ $TOTAL_FILENAMES -ne $TOTAL_IMAGES ]
then
echo "Something went wrong! Got list of $TOTAL_FILENAMES filenames but $TOTAL_IMAGES images"
exit 1
fi

for ((i=0;i<$TOTAL_IMAGES;i++))
do
FILENAME=$(python -c "print '${FILENAMES[$i]}'")
IMAGE_URL=$(python -c "print '${IMAGE_URLS[$i]}'")
echo "Downloading $FILENAME ($(($i + 1))/$TOTAL_IMAGES)"
if [ "$WGET" ]
then
"$WGET" -q "$IMAGE_URL" -O "$DESTDIR/$FILENAME"
elif [ "$CURL" ]
then
"$CURL" "$IMAGE_URLS" -o "$DESTDIR/$FILENAMES" -s
fi
done
exit 0

Wer das Script als Datei herunterladen möchte, bedient sich des folgenden Links:

2012-01-22-dropbox-gallery-download.sh

Tags: , , , ,
Labels: Linux

3 Kommentare | neuen Kommentar verfassen

Freitag, 6. Januar 2012

Die Amis können mit unserer Sauna-Kultur nichts anfangen

Als wir uns gestern mit Kollegen meiner Freundin bei Vip Oriental in Santa Clara CA zu ein paar Gläschen Joghurt-Soju trafen, lenkte meine Freundin das Gespräch auf die Sauna-Kultur in Europa. Für alle Anwesenden — notabene asiatischer Abstammung — war es eine unerträgliche Vorstellung, sich komplett nackt in eine gemischte Sauna zu setzen. Ich stand auf verlorenem Posten, die Anwesenden davon zu überzeugen, dass Splitternacktheit vor Fremden nicht den Weltuntergang bedeuten muss …

Dieses Phänomen scheint weitherum bekannt zu sein:

Walk into a London sauna and you’re likely to encounter a mixture of sauna cultures: stark-naked Scandinavians, towel-clad Brits and Americans snugly packed in their Speedos.

Quelle: How Saunas Work

Zumindest die Japaner praktizieren Nacktbaden seit Jahrhunderten — erst die Ankunft der prüden Europäer (!) bewog die Bewohner des Landes der aufgehenden Sonne, ihre öffentlichen Bäder nach Geschlechtern zu trennen.

Tags: , , ,
Labels: USA

1 Kommentar | neuen Kommentar verfassen