Befehl "wc -c" und "wc -m" unter Linux

24

Ich habe eine Textdatei, deren Inhalt ist:

i k k

Wenn ich wc -mzum Zählen von Zeichennummern in dieser Datei verwende, ist das Ergebnis 7 .

Frage 1: Aber warum habe ich 7 bekommen, sollte ich nicht " 6 " bekommen, vorausgesetzt, es zählt das " Zeilenende " -Zeichen?

Frage 2: Wie genau funktioniert das wc -m?

Frage 3: Wenn ich wc -c(zum Zählen von Byte-Nummern) verwende, erhalte ich dasselbe Ergebnis wie wc -m. Worum geht es also, wenn ich zwei verschiedene Optionen habe ? Sie machen genau den gleichen Job, nicht wahr? Wenn nicht, was ist der Unterschied und wie wc -cfunktioniert es?

SWIIWII
quelle
1
Lesen Sie Joel über das absolute Minimum, das jeder Softwareentwickler unbedingt über Unicode und Zeichensätze wissen muss (keine Ausreden!), Um Erklärungen zu Zeichen, Zeichenkodierung und Zeichensätzen zu erhalten
phuclv
1
Sie könnten auch 7 haben, wenn Sie Datei von Windows mit CRLF-Zeilenenden kam
Chris H

Antworten:

36

Sie sollten dort in der Tat nur 6 Zeichen haben. Versuche zu rennen

cat -A filename

Anzeigen der nicht druckbaren Zeichen Ihrer Datei. Sie müssen etwas extra haben. Wenn ich eine Datei wie Ihre erstelle, verstehe ich

i k k$

Hast du ein Leerzeichen gesetzt? Das würde 7 ergeben: i k k $oder vielleicht hat es eine neue Zeile:

i k k$
$

das ist auch 7

Wie du sagst

wc -m

zählt Zeichen und

wc -c

zählt Bytes. Wenn alle Ihre Zeichen Teil des ASCII-Zeichensatzes sind, gibt es nur 1 Byte pro Zeichen, sodass Sie von beiden Befehlen die gleiche Anzahl erhalten.

Probieren Sie eine Datei mit Nicht-ASCII-Zeichen aus:

$ echo ك > testfile
$ wc -m testfile
2 testfile
$ wc -c testfile
3 testfile

Aha! Jetzt mehr Bytes als Zeichen.

Zanna
quelle
3
Ich habe den Befehl " cat -A " verwendet und festgestellt, dass vor dem Zeichen " Zeilenende " ( $ ) ein Leerzeichen steht . Deshalb habe ich 7 statt 6 bekommen. Danke, die " Katze-A " hat mir sehr geholfen.
SWIIWII
2
@SWIIWII Ja, das habe ich gerade zu meiner Antwort hinzugefügt, da ich dachte, das wäre es wahrscheinlich :)
Zanna
1
Auch der Zeilenumbruch wurde gezählt. Auch wenn es irgendwie nicht sichtbar ist, ist es dennoch ein Zeichen und zählt in der Datei als Datenblock. Übrigens gute Verwendung von Katze-A. Once könnte auch hexdump oder xxd verwenden, um das gleiche zu tun
Sergiy Kolodyazhnyy
@ Serg ja, und cat -Awürde das auch zeigen. Ich fügte meiner Antwort hinzu, danke :)
Zanna
@SWIIWII Code in Backticks einfügen `likethis`, damit er lesbar ist, nicht fett
phuclv
2
$ locale charmap
UTF-8

In meiner aktuellen Umgebung ist der Zeichensatz UTF-8, dh Zeichen werden mit 1 bis 4 Bytes pro Zeichen codiert (obwohl die ursprüngliche Definition von UTF-8 zulässigen Zeichencodepunkten bis 0x7fffffff die meisten Tools UTF- 8-Byte-Sequenzen von bis zu 6 Byte).

In diesem Zeichensatz stehen alle Zeichen aus Unicode zur Verfügung, a aist beispielsweise als Bytewert 65, a als 3- éByte-Sequenz 228 185 149 und als Zwei-Byte-Sequenz 195 169 codiert .

$ printf 乕 | wc -mc
  1       3
$ printf a | wc -mc
  1       1

Jetzt:

$ export fr_FR.iso885915@euro
$ locale charmap
ISO-8859-15

Ich habe meine Umgebung geändert, in der der Zeichensatz jetzt ISO-8859-15 ist (andere Dinge wie Sprache, Währungssymbol, Datumsformat wurden ebenfalls geändert, die Sammlung dieser regionalen Einstellungen wird als Gebietsschema bezeichnet ). In dieser Umgebung muss ein neuer Terminal-Emulator gestartet werden, damit die Zeichenwiedergabe an das neue Gebietsschema angepasst werden kann.

ISO-8859-15 ist ein Einzelbyte-Zeichensatz, der nur 256 Zeichen enthält (tatsächlich werden sogar weniger als die tatsächlich abgedeckten Zeichen verwendet). Dieser spezielle Zeichensatz wird für westeuropäische Sprachen verwendet, da er die meisten seiner Sprachen (und das Euro-Symbol) abdeckt.

Es hat das aZeichen mit dem Bytewert 65 wie in UTF-8 oder ASCII, es hat auch das éZeichen (wie es beispielsweise in Französisch oder Spanisch gebräuchlich ist), aber mit dem Bytewert 233 hat es nicht das Zeichen 乕.

In dieser Umgebung wc -cund wc -mwird immer das gleiche Ergebnis geben.

In Ubuntu wie auf den meisten modernen Unix-ähnlichen Systemen ist die Standardeinstellung normalerweise UTF-8, da dies der einzige unterstützte Zeichensatz (und die einzige Codierung) ist, die den gesamten Unicode-Bereich abdeckt.

Es gibt andere Mehrbyte-Zeichenkodierungen, aber sie werden unter Ubuntu nicht so gut unterstützt, und Sie müssen durch die Rahmen gehen, um ein Gebietsschema mit diesen zu generieren, und wenn Sie dies tun, werden Sie feststellen, dass viele Dinge nicht funktionieren richtig arbeiten.

In Ubuntu sind Zeichensätze entweder Einzelbyte- oder UTF-8-Zeichensätze.

Nun noch ein paar Anmerkungen:

In UTF-8 bilden nicht alle Bytefolgen gültige Zeichen. Beispielsweise werden alle UTF-8-Zeichen, die keine ASCII-Zeichen sind, mit Bytes gebildet, für die das 8. Bit gesetzt ist, für die jedoch nur für das erste das 7. Bit gesetzt ist.

Wenn Sie eine Folge von Bytes mit dem 8. Bit gesetzt haben, von denen keines das 7. Bit gesetzt hat, kann dies nicht in ein Zeichen übersetzt werden. Und dann treten Probleme und Inkonsistenzen auf, da die Software nicht weiß, was sie damit anfangen soll. Zum Beispiel:

$ printf '\200\200\200' | wc -mc
      0       3
$ printf '\200\200\200' | grep -q . || echo no
no

wcund grepfinde keinen Charakter darin aber:

$ x=$'\200\200\200' bash -c 'echo "${#x}"'
3

bash Findet 3. Wenn einem Zeichen keine Folge von Bytes zugeordnet werden kann, wird jedes Byte als Zeichen betrachtet.

Dies kann noch komplizierter werden, da es in Unicode Codepunkte gibt, die als Zeichen ungültig sind, und einige, die keine Zeichen sind. Je nach Tool wird die UTF-8-Codierung möglicherweise als Zeichen betrachtet oder nicht.

Eine andere zu berücksichtigende Sache ist der Unterschied zwischen Charakter und Graphem und wie sie gerendert werden.

$ printf 'e\u301\u20dd\n'
é⃝
$ printf 'e\u301\u20dd' | wc -mc
      3       6

Dort haben wir 3 Zeichen als 6 Bytes als ein Graphem gerendert, weil wir 3 Zeichen zusammen haben (ein Basiszeichen, einen kombinierenden Akzent und einen kombinierenden umschließenden Kreis).

Die GNU-Implementierung von wcas, wie sie unter Ubuntu zu finden ist, verfügt über einen -LSchalter, der die Anzeigebreite der breitesten Zeile in der Eingabe angibt:

$ printf 'e\u301\u20dd\n' | wc -L
1

Sie werden auch feststellen, dass einige Zeichen in dieser Breitenberechnung 2 Zellen belegen , wie unser Zeichen von oben:

$ echo 乕 | wc -L
2

Fazit: Im wilden Wort sind Byte, Zeichen und Graphem nicht unbedingt dasselbe.

Stéphane Chazelas
quelle
1

Der Unterschied zwischen wc -cund wc -mbesteht darin, dass in einem Gebietsschema mit Multibyte-Zeichen (z. B. UTF8) das erste Byte zählt, während das zweite Zeichen zählt. Betrachten Sie die folgende Datei:

$ hexdump -C dummy.txt 
00000000  78 79 cf 80 0a                                    |xy...|

(Für diejenigen, die kein UTF8 sprechen, sind das die Buchstaben 'x', 'y' und 'π', gefolgt von einer neuen Zeile). Es ist fünf Bytes lang:

$ wc -c dummy.txt 
5 dummy.txt

aber nur vier Zeichen lang:

$ wc -m dummy.txt 
4 dummy.txt
Kennzeichen
quelle
Oder betrachten Sie auch UTF-32, wo jedes Zeichen 4 Bytes hat.
Jörg W Mittag