Komendy „wc -c” i „wc -m” w systemie Linux

24

Mam plik tekstowy, jego zawartość to:

i k k

Kiedy używam wc -mdo liczenia liczb znaków w tym pliku, wynik to 7 .

Pytanie 1: Ale dlaczego dostałem 7, czy nie powinienem dostać „ 6 ”, zakładając, że liczy się znak „ końca linii ”?

Pytanie 2: Jak dokładnie wc -mdziała?

Pytanie 3: Kiedy używam wc -c(do liczenia liczb bajtowych), mam taki sam wynik jak wc -m, więc jaki jest sens posiadania dwóch różnych opcji ? Wykonują dokładnie tę samą pracę, prawda? Jeśli nie, jaka jest różnica i jak wc -cdziała?

SWIIWII
źródło
1
Możesz mieć również 7, jeśli plik pochodzi z systemu Windows z zakończeniami linii CRLF
Chris H

Odpowiedzi:

36

Rzeczywiście powinieneś mieć tam tylko 6 znaków. Spróbuj uruchomić

cat -A filename

Aby zobaczyć niedrukowalne znaki z pliku. Musisz mieć coś dodatkowego. Widzę, że jeśli utworzę plik podobny do twojego

i k k$

Czy umieściłeś miejsce? To dałoby 7: i k k $a może ma nową linię:

i k k$
$

czyli 7

Tak jak mówisz

wc -m

liczy znaki i

wc -c

liczy bajty. Jeśli wszystkie twoje znaki są częścią zestawu znaków ASCII, to będzie tylko 1 bajt na znak, więc otrzymasz taką samą liczbę z obu poleceń.

Wypróbuj plik zawierający znaki inne niż ASCII:

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

Aha! Więcej bajtów niż znaków.

Zanna
źródło
3
Użyłem polecenia „ cat -A ” i w końcu odkryłem, że mam jedną spację przed znakiem „ end-of-line ” ( $ ). Dlatego dostałem 7 zamiast 6. Dzięki, „ cat-A ” bardzo mi pomógł.
SWIIWII,
2
@ SWIIWII Tak, właśnie dodałem to do mojej odpowiedzi, ponieważ myślałem, że to prawdopodobnie będzie :)
Zanna
1
policzono także znak nowej linii. Nawet jeśli jest to trochę niewidoczne, nadal jest postacią i liczy się w pliku jako fragment danych. Nawiasem mówiąc, dobre użycie kota -A. Kiedyś można również użyć hexdump lub xxd, aby zrobić to samo
Sergiy Kolodyazhnyy
@Serg tak i też cat -Ato pokaże. Dodałem do mojej odpowiedzi, dzięki :)
Zanna,
@SWIIWII umieścił kod w backticks, `likethis`aby był czytelny, nie pogrubiaj
phuclv
2
$ locale charmap
UTF-8

W moim obecnym środowisku zestaw znaków to UTF-8, co oznacza, że ​​znaki są kodowane od 1 do 4 bajtów na znak (chociaż ponieważ oryginalna definicja UTF-8 dopuszczała punkty kodowe do 0x7fffffff, większość narzędzi rozpoznaje UTF- 8 bajtów sekwencji do 6 bajtów).

W tym zestawie znaków wszystkie znaki z Unicode są dostępne, a ajest kodowany jako wartość bajtu 65, a jako 3 bajty 228 185 149 i na przykład éjako sekwencja dwóch bajtów 195 169.

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

Teraz:

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

Zmodyfikowałem swoje środowisko, w którym zestaw znaków to teraz ISO-8859-15 (inne rzeczy, takie jak język, symbol waluty, format daty zostały również zmodyfikowane, a zbiór ustawień regionalnych nazywany jest ustawieniami regionalnymi ). Muszę uruchomić nowy emulator terminala w tym środowisku, aby dostosować renderowanie znaków do nowych ustawień regionalnych.

ISO-8859-15 jest jednobajtowym zestawem znaków, co oznacza, że ​​ma tylko 256 znaków (w rzeczywistości nawet mniej niż te, które są rzeczywiście objęte). Ten szczególny zestaw znaków jest używany w językach Europy Zachodniej, ponieważ obejmuje większość jego języków (i symbol euro).

Ma aznak o wartości bajtu 65, jak w UTF-8 lub ASCII, ma również éznak (jak powszechnie używany na przykład w języku francuskim lub hiszpańskim), ale o wartości bajtu 233 nie ma znaku 乕.

W takim środowisku wc -ci wc -mzawsze da taki sam rezultat.

W Ubuntu, podobnie jak w większości współczesnych systemów uniksowych, domyślnie jest to UTF-8, ponieważ jest to jedyny obsługiwany zestaw znaków (i kodowanie) obejmujący cały zakres Unicode.

Istnieją inne wielobajtowe kodowania znaków, ale nie są one tak dobrze obsługiwane w Ubuntu i musisz przejść przez obręcze, aby móc wygenerować ustawienia narodowe za ich pomocą, a jeśli to zrobisz, przekonasz się, że wiele rzeczy nie działa poprawnie.

Tak więc w Ubuntu zestawy znaków są jednobajtowe lub UTF-8.

Teraz jeszcze kilka notatek:

W UTF-8 nie wszystkie sekwencje bajtów tworzą prawidłowe znaki. Na przykład wszystkie znaki UTF-8, które nie są znakami ASCII, są tworzone z bajtów, które mają ustawiony 8. bit, ale tylko pierwszy z nich ma ustawiony 7. bit.

Jeśli masz sekwencję bajtów z 8. bitowym zestawem, z których żaden nie ma 7. bitowego zestawu, to nie można go przetłumaczyć na znak. I wtedy zaczynają się problemy i niespójności, ponieważ oprogramowanie nie wie, co z nimi zrobić. Na przykład:

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

wci grepnie znajdź tam żadnej postaci, ale:

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

bash znajduje 3. Gdy nie może odwzorować sekwencji bajtów na znak, uważa każdy bajt za znak.

Może się jeszcze bardziej skomplikować, ponieważ w Unicode występują punkty kodowe, które są niepoprawne jako znaki, a niektóre, które nieznakami , i w zależności od narzędzia ich kodowanie UTF-8 może, ale nie musi, być traktowane jako znak.

Inną rzeczą, którą należy wziąć pod uwagę, jest różnica między charakterem a grafhem oraz sposób ich renderowania.

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

Tam mamy 3 znaki jako 6 bajtów renderowane jako jeden grafhem, ponieważ mamy 3 znaki połączone razem (jeden znak podstawowy, łączący ostry akcent i łączący się okrąg).

Implementacja GNU, wctaka jak w Ubuntu, ma -Lprzełącznik informujący o szerokości wyświetlania najszerszej linii na wejściu:

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

Przekonasz się również, że niektóre postacie zajmują 2 komórki w tym obliczeniu szerokości, jak nasza postać z góry:

$ echo 乕 | wc -L
2

Podsumowując: bardziej zwięźle, bajt, charakter i grafhem niekoniecznie są takie same.

Stéphane Chazelas
źródło
1

Różnica między wc -ci wc -mpolega na tym, że w ustawieniach regionalnych ze znakami wielobajtowymi (powiedzmy UTF8), pierwsze z nich liczy bajty, a drugie zlicza znaki. Rozważ następujący plik:

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

(dla tych, którzy nie mówią UTF8, są to litery „x”, „y” i „π”, po których następuje nowa linia). Ma pięć bajtów:

$ wc -c dummy.txt 
5 dummy.txt

ale tylko cztery znaki:

$ wc -m dummy.txt 
4 dummy.txt
znak
źródło
Lub rozważmy nawet UTF-32, gdzie każdy znak ma 4 bajty.
Jörg W Mittag