meillo@6: Das Werkzeugkaestle, #1 meillo@0: meillo@6: cut - cut out selected fields of each line of a file meillo@6: ---------------------------------------------------- meillo@6: markus schnalke meillo@6: 2015-05 meillo@0: meillo@0: meillo@1: Cut ist ein klassisches Programm im Unix-Werkzeugkasten. meillo@8: In keinem ordentlichen Tutorial zur Shellprogrammierung fehlt meillo@8: es. Es ist ein schoenes Anschauungsobjekt fuer's Shellscripting. meillo@8: Hier soll ein wenig hinter die Fassade von cut geschaut werden. meillo@0: meillo@0: meillo@4: Funktionsweise meillo@4: meillo@8: Urspruenglich hatte cut zwei Modi, die spaeter um einen dritten meillo@8: erweitert wurden. Cut schneidet entweder bestimmte Zeichen aus meillo@8: den Zeilen der Eingabe oder bestimmte, durch Trennzeichen meillo@8: definierte, Felder. meillo@0: meillo@8: Der Zeichenmodus ist geeignet um Festbreitenformaten zu meillo@8: zerteilen. So kann man damit beispielsweise bestimmte meillo@8: Zugriffsrechte aus der Ausgabe von `ls -l' ausschneiden. Hier meillo@8: die Rechte des Besitzers: meillo@0: meillo@4: $ ls -l foo | cut -c 2-4 meillo@4: rw- meillo@0: meillo@4: Oder die Schreibrechte des Besitzers, der Gruppe und der meillo@4: Welt: meillo@0: meillo@4: $ ls -l | cut -c 3,6,9 meillo@4: ww- meillo@0: meillo@4: Mit cut lassen sich aber auch Strings kuerzen. meillo@0: meillo@6: $ echo "$long" | cut -c -20 meillo@0: meillo@8: Dieser Befehl gibt die ersten maximal 20 Zeichen von meillo@8: `$long' aus. (Alternativ kann man hierfuer auch `printf meillo@8: "%.20s\n" "$long"' verwenden.) meillo@0: meillo@4: Geht es aber nicht um die Darstellung von Zeichen, sondern um meillo@8: ihre Speicherung, dann ist `-c' nicht unbedingt geeignet. meillo@8: Frueher, als US-ASCII als Zeichensatz und -kodierung meillo@4: noch omnipraesent war, wurde jedes Zeichen mit genau einem meillo@4: Byte gespeichert. Somit selektierte `cut -c' gleichermassen meillo@4: sowohl Ausgabezeichen als auch Bytes. Mit dem Aufkommen von meillo@4: Multibyte-Kodierungen (wie UTF-8) musste man sich jedoch von meillo@4: dieser Annahme loesen. In diesem Zug bekam cut mit meillo@8: POSIX.2-1992 einen Bytemodus mit der Option `-b'. Will man meillo@4: also nur die ersten maximal 500 Bytes vor dem meillo@0: Newline-Zeichen stehen haben (und den Rest stillschweigend meillo@0: ignorieren), dann macht man das mit: meillo@0: meillo@6: $ cut -b -500 meillo@0: meillo@4: Den Rest kann man sich mit `cut -b 501-' einfangen. Diese meillo@8: Funktion ist insbesondere fuer POSIX wichtig, da man so meillo@8: Textdateien mit begrenzter Zeilenlaenge erzeugen kann. meillo@4: [ http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cut.html#tag_20_28_17 meillo@0: meillo@0: Neben dem Zeichen- bzw. Byte-Modus bietet cut noch den meillo@8: Feld-Modus, den man mit `-f' einleitet. Mit ihm meillo@4: koennen Felder ausgewaehlt werden. Das Trennzeichen (per meillo@4: Default der Tab) kann mit `-d' geaendert werden. meillo@0: meillo@8: Der typische Anwendungsfall fuer cut im Feld-Modus ist die meillo@8: Auswahl von Information aus der passwd-Datei. So z.B. der meillo@8: Benutername, seine ID und das Homeverzeichnis: meillo@0: meillo@6: $ cut -d: -f1,3,6 /etc/passwd meillo@0: meillo@0: (Die Argumente fuer die Optionen koennen bei cut uebrigens meillo@8: mit Whitespace abgetrennt oder direkt angehaengt folgen.) meillo@0: meillo@0: meillo@4: Dieser Feld-Modus ist fuer einfache tabellarische Dateien, meillo@4: wie eben die passwd, gut geeignet. Er kommt aber schnell an meillo@0: seine Grenzen. Gerade der uebliche Fall, dass an Whitespace meillo@0: in Felder geteilt werden soll, wird damit nicht abgedeckt. meillo@0: Der Delimiter kann nur genau ein Zeichen sein. Es kann also meillo@0: nicht sowohl an Leerzeichen als auch an Tabs getrennt werden. meillo@0: Auch unterteilt cut an jedem Trennzeichen. Zwei aneinander meillo@4: stehende Trennzeichen fuehren zu einem leeren Feld. Dieses meillo@8: Verhalten widerspricht den Erwartungen, die man an die meillo@8: Verarbeitung einer Datei mit Whitespace-getrennten Feldern meillo@8: hat. Manche Implementierungen von cut, z.B. die von FreeBSD, meillo@8: haben Erweiterungen, die das gewuenschte Verhalten fuer meillo@8: Whitespace-getrennte Felder bieten. Ansonsten, d.h. wenn meillo@8: man portabel bleiben will, hilft awk. meillo@0: meillo@4: Awk bietet noch eine weitere Funktion, die cut missen meillo@8: laesst: Das Tauschen der Feld-Reihenfolge in der Ausgabe. Bei meillo@8: cut ist die Reihenfolge der Feldauswahlangabe irrelevant; ein meillo@8: Feld kann selbst mehrfach angegeben werden. So gibt der Aufruf meillo@8: von `cut -c 5-8,1,4-6' die Zeichen Nummer 1, 4, 5, 6, 7 und 8 meillo@8: in genau dieser Reihenfolge aus. Die Auswahl entspricht damit meillo@8: der Mengenlehre in der Mathematik: Jedes angegebene Feld wird meillo@8: Teil der Ergebnismenge sein. Die Felder der Ergebnismenge sind meillo@8: dabei immer gleich geordnet wie sie es in der Eingabe waren. meillo@8: Oder, um die Worte der Manpage in Version 8 Unix meillo@8: wiederzugeben: ``In data base parlance, it projects a relation.'' meillo@8: Cut fuehrt also die Datenbankoperation Projektion auf meillo@8: Textdateien aus. Die Wikipedia erklaert das in meillo@8: verstaendlicherer Sprache: meillo@7: meillo@7: Die Projektion entspricht der Projektionsabbildung aus der meillo@7: Mengenlehre und kann auch Attributbeschränkung genannt meillo@7: werden. Sie extrahiert einzelne Attribute aus der meillo@7: ursprünglichen Attributmenge und ist somit als eine Art meillo@7: Selektion auf Spaltenebene zu verstehen, das heißt, die meillo@7: Projektion blendet Spalten aus. meillo@7: meillo@8: [ http://de.wikipedia.org/wiki/Projektion_(Informatik)#Projektion meillo@8: meillo@7: meillo@7: meillo@7: meillo@0: Geschichtliches meillo@0: meillo@4: Cut erblickte 1982 mit dem Release von UNIX System III das meillo@4: Licht der oeffentlichen Welt. Wenn man die Quellen von System meillo@4: III durchforstet, findet man die Quellcodedatei cut.c mit dem meillo@4: Zeitstempel 1980-04-11. meillo@1: [ http://minnie.tuhs.org/cgi-bin/utree.pl?file=SysIII/usr/src/cmd meillo@4: Das ist die aelteste Manifestation des Programms, die ich meillo@8: aufstoebern konnte. Allerdings spricht die sccsid im meillo@8: Quellcode von Version 1.5. Es muss also noch eine meillo@8: Vorgeschichte geben. Zu dieser habe ich leider keinen Zugang meillo@8: gefunden. meillo@0: meillo@1: Aber werfen wir doch einen Blick auf die BSD-Linie: Dort ist mein meillo@8: fruehester Fund ein cut.c mit dem Dateimodifikationsdatum meillo@8: 1986-11-07 meillo@8: [ http://minnie.tuhs.org/cgi-bin/utree.pl?file=4.3BSD-UWisc/src/usr.bin/cut meillo@8: als Teil der Spezialversion 4.3BSD-UWisc, meillo@6: [ http://gunkies.org/wiki/4.3_BSD_NFS_Wisconsin_Unix meillo@6: die im Januar 1987 veroeffentlicht wurde. meillo@8: Die Implementierung unterscheidet sich nur minimal von der meillo@8: in System III. meillo@8: Im bekannteren 4.3BSD-Tahoe (1988) taucht cut nicht auf. meillo@8: Das darauf folgende 4.3BSD-Reno (1990) liefert aber wieder meillo@8: ein cut mit aus. Dieses cut ist ein von Adam S. Moskowitz und meillo@8: Marciano Pitargue neu implementiertes cut, das 1989 in BSD meillo@8: aufgenommen wurde. meillo@1: [ http://minnie.tuhs.org/cgi-bin/utree.pl?file=4.3BSD-Reno/src/usr.bin/cut meillo@4: Seine Manpage meillo@1: [ http://minnie.tuhs.org/cgi-bin/utree.pl?file=4.3BSD-Reno/src/usr.bin/cut/cut.1 meillo@4: erwaehnt bereits die erwartete Konformitaet mit POSIX.2. meillo@4: Nun sollte man wissen, dass POSIX.2 erst im September meillo@8: 1992 veroeffentlicht wurde, also gut zwei Jahren *nachdem* die meillo@8: Manpage und das Programm geschrieben wurden. Das Programm meillo@8: wurde also anhand von Arbeitsversionen des Standards meillo@4: implementiert. Zweieinhalb Jahre Arbeit war immerhin schon in meillo@4: den Standardisierungsprozess geflossen; bis zur meillo@8: Fertigstellung sollte es aber noch weitere zwei Jahre dauern. meillo@0: meillo@1: Trotz all dieser Jahreszahlen aus den 80er Jahren gehoert cut meillo@1: aus Sicht des urspruenglichen Unix zu den juengeren Tools. meillo@1: Wenn cut auch ein Jahrzehnt aelter als Linux, der Kernel, ist, meillo@4: so war Unix doch schon ueber zehn Jahre alt, als cut das meillo@4: erste Mal auftauchte. Insbesondere gehoerte cut noch nicht meillo@4: zu Version 7 Unix, das die Ausgangsbasis aller modernen meillo@4: Unix-Systeme darstellt. Die weit komplexeren Programme sed meillo@4: und awk waren dort schon vertreten. Man muss sich also meillo@4: fragen, warum cut ueberhaupt noch entwickelt wurde, wo es meillo@8: schon zwei Programme gab, die die Aufgabe von cut abdeckten. meillo@8: Ein Argument fuer cut war sicher seine Kompaktheit und meillo@4: die damit verbundene Geschwindigkeit gegenueber dem damals meillo@4: traegen awk. Diese schlanke Gestalt ist es auch, die der Unix meillo@4: Philosopie entspricht: Mache eine Aufgabe und die richtig! meillo@4: So bewaehrte sich cut. Es wurde in andere Unix Varianten meillo@4: uebernommen, standardisiert und ist heutzutage ueberall meillo@1: anzutreffen. meillo@1: meillo@8: Die urspruengliche Variante (ohne -b) tauchte schon 1985 in meillo@5: der System V Interface Definition, einer wichtigen formalen meillo@5: Beschreibung von UNIX System V, und in allen relevanten meillo@5: Standards seither auf. Mit POSIX.2 im Jahre 1992 wurde cut meillo@5: zum ersten Mal in der heutigen Form (mit -b) standardisiert. meillo@1: meillo@1: meillo@8: meillo@8: Multibyte-Behandlung meillo@8: meillo@8: Nun sind der Bytemodus und die damit verbundene meillo@8: Multibyte-Verarbeitung des POSIX-Zeichenmodus bereits seit meillo@8: 1992 standardisiert, wie steht es aber mit deren Umsetzung? meillo@8: Welche Versionen implementieren denn den POSIX korrekt? meillo@8: Die Situation ist mehrschichtig. Es gibt traditionelle meillo@8: Implementierungen, die nur -c und -f kennen. Dann gibt es meillo@8: Implementierungen die zwar -b kennen, es aber nur als Alias meillo@8: fuer -c handhaben. Diese Implementierungen funktionieren mit meillo@8: Single-Byte-Encodings (z.B. US-ASCII, Latin1) korrekt, bei meillo@8: Multi-Byte-Encodings (z.B. UTF-8) verhaelt sich ihr -c aber meillo@8: wie -b (und -n wird ignoriert). Schliesslich gibt es noch meillo@8: Implementierungen, die -b und -c tatsaechlich POSIX-konform meillo@8: implementieren. meillo@8: meillo@8: Traditionelle Zwei-Modi-Implementierungen sind z.B. die von meillo@8: System III, System V und die aller BSDs bis in die 90er. meillo@8: meillo@8: Pseude-Multibyte-Implementierungen bieten GNU und die meillo@8: modernen NetBSDs und OpenBSDs. Wie sehr dort der Schein von meillo@8: POSIX-konformitaet gewahrt wird, ist unterschiedlich. Nicht meillo@8: immer findet man klare Aussagen wie diese: meillo@8: meillo@8: /* Since we don't support multi-byte characters, the -c and -b meillo@8: options are equivalent, and the -n option is meaningless. */ meillo@8: meillo@8: [ XXX meillo@8: meillo@8: Tatsaechlich standardkonforme Implementierungen, die meillo@8: Multibytes korrekt handhaben, bekommt man bei einem modernen meillo@8: FreeBSD und bei den Heirloom Tools. Bei FreeBSD hat Tim Robbins meillo@8: (tjr) im Sommer 2004 den Zeichenmodus POSIX-konform reimplementiert. meillo@8: [ https://svnweb.freebsd.org/base?view=revision&revision=131194 meillo@8: Warum die beiden anderen grossen BSDs diese Aenderung nicht meillo@8: uebernommen haben, bleibt offen. Es scheint aber an der im meillo@8: obigen Kommentar formulierten Grundausrichtung zu liegen. meillo@8: meillo@8: Wie findet man als Nutzer heraus, ob beim cut(1) des eigenen meillo@8: Systems Multibytes korrekt unterstuetzt werden? Zuerst ist meillo@8: entscheidend, ob das System selbst mit einem Multibyte-Encoding meillo@8: arbeitet, denn tut es das nicht, dann entsprechen sich Zeichen meillo@8: und Bytes und die Frage eruebrigt sich. Man kann dazu nachschauen, meillo@8: welches Locale eingestellt ist, aber einfacher ist es, ein meillo@8: typisches Mehrbytezeichen, wie z.B. einen Umlaut, auszugeben meillo@8: und zu schauen ob dieses in einem oder in mehreren Bytes meillo@8: kodiert ist: meillo@8: meillo@8: $ echo ä | od -c meillo@8: 0000000 303 244 \n meillo@8: 0000003 meillo@8: meillo@8: In diesem Fall sind es zwei Bytes: oktal 303 und 244 . (Den meillo@8: Zeilenumbruch fuegt echo(1) hinzu.) meillo@8: meillo@8: Mit dem Programm iconv(1) kann man Test explizit in bestimmte meillo@8: Kodierungen konvertieren. Hier Beispiele, wie das Ergebnis meillo@8: bei Latin1 und wie es bei UTF-8 aussieht. meillo@8: meillo@8: $ echo ä | iconv -t latin1 | od -c meillo@8: 0000000 344 \n meillo@8: 0000002 meillo@8: meillo@8: $ echo ä | iconv -t utf8 | od -c meillo@8: 0000000 303 244 \n meillo@8: 0000003 meillo@8: meillo@8: Die Ausgabe auf dem eigenen System (ohne die iconv-Konvertierung) meillo@8: wird recht sicher einer dieser beiden Ausgaben entsprechen. meillo@8: meillo@8: Nun zum Test der cut-Implementierung. Hat man ein UTF-8-System, meillo@8: dann sollte sich eine POSIX-konforme Implementierung so verhalten: meillo@8: meillo@8: $ echo aä | ./cut -c -2 | od -c meillo@8: 0000000 a 303 244 \n meillo@8: 0000004 meillo@8: meillo@8: $ echo aä | ./cut -b -2 | od -c meillo@8: 0000000 a 303 \n meillo@8: 0000003 meillo@8: meillo@8: $ echo aä | ./cut -b -2 -n | od -c meillo@8: 0000000 a \n meillo@8: 0000002 meillo@8: meillo@8: Bei einer Implementierung, die -b und -c gleich behandelt, meillo@8: ist die Ausgabe in allen drei Faellen wie die mittlere: Es meillo@8: werden die ersten beiden Bytes ausgegeben. meillo@8: meillo@8: meillo@8: meillo@8: Implementierungen meillo@8: meillo@8: Nun zum Blick auf den Code. Hier soll eine Auswahl an meillo@8: Implementierungen etwas genauer betrachtet werden. Fuer einen meillo@8: ersten Eindruck ist der Umfang des Quellcodes hilfreich. meillo@8: Typischerweise steigt dieser ueber die Jahre an. Diese meillo@8: Beobachtung kann hier in der Tendenz, aber nicht in jedem Fall, meillo@8: bestaetigt werden. meillo@8: meillo@8: Die Unterstuetzung des Byte-Modus (-b) erfordert zwangslaeufig meillo@8: mehr Code, deshalb ist zu erwarten, dass diejenigen meillo@8: Implementierungen, die ihn haben, umfangreicher sind. meillo@8: meillo@8: Codevergleich meillo@8: meillo@8: SLOC Zeilen Bytes Gehoert zu Dateidatum Kategorie meillo@8: ----------------------------------------------------------------- meillo@8: 116 123 2966 System III 1980-04-11 (trad) meillo@8: 118 125 3038 4.3BSD-UWisc 1986-11-07 (trad) meillo@8: 200 256 5715 4.3BSD-Reno 1990-06-25 (trad) meillo@8: 200 270 6545 NetBSD 1993-03-21 (trad) meillo@8: 218 290 6892 OpenBSD 2008-06-27 (pseudo) meillo@8: 224 296 6920 FreeBSD 1994-05-27 (trad) meillo@8: 232 306 7500 NetBSD 2014-02-03 (pseudo) meillo@8: 340 405 7423 Heirloom 2012-05-20 (POSIX) meillo@8: 382 586 14175 GNU coreutils 1992-11-08 (pseudo) meillo@8: 391 479 10961 FreeBSD 2012-11-24 (POSIX) meillo@8: 588 830 23167 GNU coreutils 2015-05-01 (pseudo) meillo@8: meillo@8: meillo@8: $ awk -F' +' '{printf("%d\t%d (%.2f)\t%d (%.2f)\t%s\t%s\t%s\n", meillo@8: $1, $2, $2/$1, $3, $3/$1, $4, $5, $6);}'