Jak mogę przyciąć plik tekstowy (zakodowany w UTF-8) do podanej liczby znaków? Nie dbam o długości linii, a cięcie może być w środku słowa.
cut
wydaje się działać na liniach, ale chcę cały plik.head -c
używa bajtów, a nie znaków.
Jak mogę przyciąć plik tekstowy (zakodowany w UTF-8) do podanej liczby znaków? Nie dbam o długości linii, a cięcie może być w środku słowa.
cut
wydaje się działać na liniach, ale chcę cały plik.head -c
używa bajtów, a nie znaków.Odpowiedzi:
Niektóre systemy mają truncate
polecenie, które obcina pliki do pewnej liczby bajtów (nie znaków).
Nie znam żadnego takiego skrótu do wielu znaków, choć możesz skorzystać z tego, perl
który jest domyślnie instalowany w większości systemów:
perl -Mopen=locale -ne '
BEGIN{$/ = \1234} truncate STDIN, tell STDIN; last' <> "$file"
Z -Mopen=locale
używamy pojęcia narodowa co postacie są (tak w lokalizacjach przy użyciu kodowania UTF-8, to kodowanie UTF-8 znaków). Zamień na, -CS
jeśli chcesz, aby wejścia / wyjścia były dekodowane / kodowane w UTF-8 niezależnie od zestawu znaków lokalizacji.
$/ = \1234
: ustawiamy separator rekordów na odwołanie do liczby całkowitej, która jest sposobem na określenie rekordów o stałej długości (w liczbie znaków ).
następnie po przeczytaniu pierwszego rekordu obcinamy stdin na miejscu (czyli na końcu pierwszego rekordu) i kończymy.
Z GNU sed
możesz to zrobić (zakładając, że plik nie zawiera NUL znaków lub sekwencji bajtów, które nie tworzą prawidłowych znaków - oba powinny być prawdziwe dla plików tekstowych):
sed -Ez -i -- 's/^(.{1234}).*/\1/' "$file"
Jest to jednak znacznie mniej wydajne, ponieważ odczytuje plik w całości i przechowuje go w pamięci i zapisuje nową kopię.
To samo z GNU awk
:
awk -i inplace -v RS='^$' -e '{printf "%s", substr($0, 1, 1234)}' -E /dev/null "$file"
-e code -E /dev/null "$file"
jest jednym ze sposobów przekazywania dowolnych nazw plików gawk
RS='^$'
: tryb slurp .Z ksh93
, bash
lub zsh
(z powłok innych niż zsh
przy założeniu, że zawartość nie zawiera NUL bajty):
content=$(cat < "$file" && echo .) &&
content=${content%.} &&
printf %s "${content:0:1234}" > "$file"
Z zsh
:
read -k1234 -u0 s < $file &&
printf %s $s > $file
Lub:
zmodload zsh/mapfile
mapfile[$file]=${mapfile[$file][1,1234]}
Za pomocą ksh93
lub bash
(uważaj, to fałszywe dla znaków wielobajtowych w kilku wersjachbash
):
IFS= read -rN1234 s < "$file" &&
printf %s "$s" > "$file"
ksh93
może także obciąć plik na miejscu zamiast przepisywać go za pomocą <>;
operatora przekierowania:
IFS= read -rN1234 0<>; "$file"
Aby wydrukować pierwsze 1234 znaki, inną opcją może być konwersja na kodowanie ze stałą liczbą bajtów na znak, np. UTF32BE
/ UCS-4
:
iconv -t UCS-4 < "$file" | head -c "$((1234 * 4))" | iconv -f UCS-4
head -c
nie jest standardowe, ale dość powszechne. Standardowy ekwiwalent byłby, dd bs=1 count="$((1234 * 4))"
ale byłby mniej wydajny, ponieważ odczytywałby dane wejściowe i zapisywał jeden bajt na raz¹. iconv
to standardowe polecenie, ale nazwy kodowania nie są znormalizowane, więc możesz znaleźć systemy bez niegoUCS-4
W każdym razie, mimo że wynik miałby najwyżej 1234 znaków, może nie być poprawnym tekstem, ponieważ prawdopodobnie zakończyłby się linią nieprzydzieloną.
Zwróć też uwagę, że chociaż te rozwiązania nie wycinałyby tekstu na środku znaku, mogłyby go złamać na środku grafemu , na przykład é
wyrażonego jako U + 0065 U + 0301 ( e
po którym następuje łączenie ostrego akcentu), lub grafule sylab Hangul w ich rozłożonych formach.
¹ i na wejściu potoku nie można bs
niezawodnie używać wartości innych niż 1, chyba że użyjesz iflag=fullblock
rozszerzenia GNU, podobnie dd
jak krótkie odczyty, jeśli odczytuje potok szybciej niż iconv
go wypełnia
dd bs=1234 count=4
Jeśli wiesz, że plik tekstowy zawiera Unicode zakodowany jako UTF-8, musisz najpierw zdekodować UTF-8, aby uzyskać sekwencję jednostek znaków Unicode i podzielić je.
Wybrałbym Python 3.x do tego zadania.
W Pythonie 3.x funkcja open () ma dodatkowy argument słowo-klucz encoding=
do odczytu plików tekstowych . Opis metody io.TextIOBase.read () wygląda obiecująco.
Więc używając Python 3 wyglądałoby to tak:
truncated = open('/path/to/file.txt', 'rt', encoding='utf-8').read(1000)
Oczywiście prawdziwe narzędzie dodaje argumenty wiersza poleceń, obsługę błędów itp.
W Pythonie 2.x można zaimplementować własny obiekt podobny do pliku i dekodować plik wejściowy wiersz po wierszu.
Chciałbym dodać inne podejście. Prawdopodobnie nie najlepsza pod względem wydajności i znacznie dłuższa, ale łatwa do zrozumienia:
#!/bin/bash
chars="$1"
ifile="$2"
result=$(cat "$ifile")
rcount=$(echo -n "$result" | wc -m)
while [ $rcount -ne $chars ]; do
result=${result::-1}
rcount=$(echo -n "$result" | wc -m)
done
echo "$result"
Wywołaj to za pomocą $ ./scriptname <desired chars> <input file>
.
To usuwa ostatni znak jeden po drugim, dopóki cel nie zostanie osiągnięty, co wydaje się naprawdę złą wydajnością, szczególnie w przypadku większych plików. Chciałem tylko przedstawić to jako pomysł, aby pokazać więcej możliwości.
wc
liczy się liczba bajtów ogółem O (n ^ 2) dla punktu docelowego w połowie pliku. Powinno być możliwe wyszukiwanie binarne zamiast wyszukiwania liniowego przy użyciu zmiennej, którą zwiększasz lub zmniejszasz, lubisz echo -n "${result::-$chop}" | wc -m
coś. (I kiedy już to robisz, zapewnij sobie bezpieczeństwo, nawet jeśli zawartość pliku zaczyna się od -e
czegoś, może coś używa printf
). Ale nadal nie pokonasz metod, które patrzą na każdy znak wejściowy tylko raz, więc prawdopodobnie nie warto.
$result
dopóki nie dopasuje on pożądanej długości, ale jeśli pożądana długość jest dużą liczbą, jest ona równie nieefektywna.
$desired_chars
bajtów na dolnym końcu, a może 4*$desired_chars
na górnym końcu. Ale nadal uważam, że najlepiej jest użyć czegoś zupełnie innego.
cut
wciąż nie obsługuje znaków wielobajtowych. Jeśli tak, możesz to zrobićcut -zc-1234 | tr -d '\0'
.