Jak wydrukować liczbę znaków w każdym wierszu pliku tekstowego

83

Chciałbym wydrukować liczbę znaków w każdym wierszu pliku tekstowego za pomocą polecenia unix. Wiem, że z PowerShell jest to proste

gc abc.txt | % {$_.length}

ale potrzebuję polecenia unix.

vikas368
źródło

Odpowiedzi:

155

Użyj Awk.

awk '{ print length }' abc.txt
Fred Foo
źródło
2
Jest to o kilka rzędów wielkości szybsze niż zastosowanie wc -c do każdej linii!
aerijman
@aerijman w przypadku tego typu problemów liczba utworzonych procesów jest zazwyczaj tym, co powoduje największą różnicę w wydajności.
MarcH
Jeśli wiersz w pliku zawiera emotikony, nie da to oczekiwanej długości.
user5507535
@ user5507535, to zależy od tego, jakiej „długości” faktycznie oczekujesz. Istnieje wiele możliwych definicji Unicode (mawk używa bajtów, nie sprawdza gawk).
Jan Hudec
16
while IFS= read -r line; do echo ${#line}; done < abc.txt

Jest to POSIX, więc powinno działać wszędzie.

Edycja: dodano -r zgodnie z sugestią Williama.

Edycja: uważaj na obsługę Unicode. Bash i zsh, z prawidłowo ustawionymi ustawieniami lokalnymi, pokażą liczbę punktów kodowych, ale myślnik pokaże bajty - więc musisz sprawdzić, co robi twoja powłoka. I tak istnieje wiele innych możliwych definicji długości w Unicode, więc zależy to od tego, czego naprawdę chcesz.

Edycja: przedrostek z, IFS=aby uniknąć utraty początkowych i końcowych spacji.

Jan Hudec
źródło
+1, ale ... to się nie powiedzie, jeśli dane wejściowe zawierają „\”. Użyj read -r
William Pursell
Jeśli wiersz w pliku zawiera emotikony, nie da to oczekiwanej długości.
user5507535
@ user5507535, właściwie to zależy od oczekiwanej „długości”. Istnieje wiele możliwych definicji Unicode (ale w tym przypadku różne powłoki będą faktycznie działać inaczej).
Jan Hudec
Zawsze ustawiaj IFS=w readpoleceniu, gdy chcesz wczytać dowolne dane. A więc IFS= read -r. readużywa IFSdo dzielenia słów i mimo że wszystkie podzielone słowa są następnie wklejane z powrotem do jednej dostępnej zmiennej ( line), nie ma gwarancji, że zostaną wklejone z powrotem razem ze wszystkimi oryginalnymi znakami separatora, które posiadały lub tylko jednym potencjalnie innym jedynki. Na przykład przy domyślnym IFS linia foo barmoże stać się foo bartracąc 7 spacji. (Tak jak w przypadku, gdy przepełnienie stosu stracił sąsiednie spacje w tym przykładowym ciągu w tym komentarzu).
mtraceur
@mtraceur, dokumentacja wyraźnie mówi, że „pozostałe słowa i ich separatory są przypisane do nazwiska”, więc są one wklejane z powrotem razem z oryginalnym separatorem. To jednak nie dotyczy wiodących i końcowych ograniczników, które są rzeczywiście utracone. Więc masz rację, IFSpowinieneś być ustawiony, ale problem, gdy tak nie jest, jest bardziej subtelny.
Jan Hudec
4

Wypróbowałem inne odpowiedzi wymienione powyżej, ale są one bardzo dalekie od przyzwoitych rozwiązań w przypadku dużych plików - zwłaszcza gdy rozmiar pojedynczej linii zajmuje więcej niż ~ 1/4 dostępnej pamięci RAM.

Zarówno bash, jak i awk siorbią całą linię, mimo że w przypadku tego problemu nie jest to potrzebne. Bash wyświetli błąd, gdy linia będzie zbyt długa, nawet jeśli masz wystarczająco dużo pamięci.

Zaimplementowałem niezwykle prosty, dość niezoptymalizowany skrypt Pythona, który podczas testowania z dużymi plikami (~ 4 GB na wiersz) nie sypie i jest zdecydowanie lepszym rozwiązaniem niż podane.

Jeśli jest to kod krytyczny czasowo dla produkcji, możesz przepisać pomysły w C lub przeprowadzić lepszą optymalizację przy wywołaniu odczytu (zamiast czytać tylko jeden bajt na raz), po przetestowaniu, że jest to rzeczywiście wąskie gardło.

Kod zakłada, że ​​znak nowej linii to znak wysuwu wiersza, co jest dobrym założeniem dla Uniksa, ale YMMV w systemie Mac OS / Windows. Upewnij się, że plik kończy się znakiem nowej linii, aby zapewnić, że liczba znaków w ostatnim wierszu nie zostanie przeoczona.

from sys import stdin, exit

counter = 0
while True:
    byte = stdin.buffer.read(1)
    counter += 1
    if not byte:
        exit()
    if byte == b'\x0a':
        print(counter-1)
        counter = 0
Samuel Liew
źródło
1
Pytanie dotyczyło pliku „tekstowego”. Nie sądzę, aby 4 GB na wiersz pasował do jakiejkolwiek rozsądnej definicji pliku tekstowego.
MarcH
3

Oto przykład użycia xargs:

$ xargs -d '\n' -I% sh -c 'echo % | wc -c' < file
kenorb
źródło
To „echo%” nie obsługuje niebezpiecznych znaków, które wymagają cytowania z powłoki. Dodatkowo "xargs" będzie dzielił twój plik na spacje i znaki nowej linii, a nie tylko znaki nowej linii, jak żądał oryginalny plakat.
bydło
1

Spróbuj tego:

while read line    
do    
    echo -e |wc -m      
done <abc.txt    
Rahul
źródło
Miałeś na myśli echo -e | wc -m, prawda? To bezużyteczne użycie poleceń; powłoka może liczyć znaki w zmiennej. Plus echo -ejest całkowicie niekompatybilny i działa w połowie powłok, podczas gdy rozpoczęcie od jednej sekwencji ucieczki działa w innej, a nic w pozostałych.
Jan Hudec