Jak obciąć plik do maksymalnej liczby znaków (nie bajtów)


13

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.

Zauważ, że implementacja GNU cutwciąż nie obsługuje znaków wielobajtowych. Jeśli tak, możesz to zrobić cut -zc-1234 | tr -d '\0'.
Stéphane Chazelas

Jak chcesz obsługiwać emoji? Niektóre są więcej niż jedną postacią ... stackoverflow.com/questions/51502486/
phuzi

2
Jaka jest postać niektóre symbole używają kilku punktów kodowych,
Jasen

Odpowiedzi:


14

Niektóre systemy mają truncatepolecenie, 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, perlktóry jest domyślnie instalowany w większości systemów:

perl

perl -Mopen=locale -ne '
  BEGIN{$/ = \1234} truncate STDIN, tell STDIN; last' <> "$file"
  • Z -Mopen=localeużywamy pojęcia narodowa co postacie są (tak w lokalizacjach przy użyciu kodowania UTF-8, to kodowanie UTF-8 znaków). Zamień na, -CSjeś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.

GNU sed

Z GNU sedmoż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ę.

GNU awk

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 .

Wbudowane powłoki

Z ksh93, bashlub zsh(z powłok innych niż zshprzy 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ą ksh93lub bash(uważaj, to fałszywe dla znaków wielobajtowych w kilku wersjachbash ):

IFS= read -rN1234 s < "$file" &&
  printf %s "$s" > "$file"

ksh93może także obciąć plik na miejscu zamiast przepisywać go za pomocą <>;operatora przekierowania:

IFS= read -rN1234 0<>; "$file"

iconv + głowa

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 -cnie 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¹. iconvto standardowe polecenie, ale nazwy kodowania nie są znormalizowane, więc możesz znaleźć systemy bez niegoUCS-4

Notatki

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 ( epo 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 bsniezawodnie używać wartości innych niż 1, chyba że użyjesz iflag=fullblockrozszerzenia GNU, podobnie ddjak krótkie odczyty, jeśli odczytuje potok szybciej niż iconvgo wypełnia


mógł zrobićdd bs=1234 count=4
Jasen

2
@Jasen, to nie byłoby wiarygodne. Zobacz edycję.
Stéphane Chazelas

Łał! przydałoby się mieć w pobliżu! Myślałem, że znam wiele przydatnych poleceń Uniksa, ale jest to niesamowita lista świetnych opcji.
Mark Stewart

5

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.


Tak, mógłbym to zrobić. Ale dotyczy to maszyn budujących CI, więc chciałbym jeszcze raz użyć standardowej komendy Linux.
Pitel

5
Cokolwiek oznacza „standardowy Linux” w twoim smaku Linuksa ...
Michael Ströder

1
Rzeczywiście, Python, mimo wszystko jakaś wersja, jest obecnie dość standardowy.
muru

Zredagowałem już swoją odpowiedź za pomocą fragmentu kodu dla Pythona 3, który może jawnie przetwarzać pliki tekstowe.
Michael Ströder

0

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.


Tak, to zdecydowanie okropne dla wydajności. W przypadku pliku o długości n wcliczy 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 -mcoś. (I kiedy już to robisz, zapewnij sobie bezpieczeństwo, nawet jeśli zawartość pliku zaczyna się od -eczegoś, 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.
Peter Cordes,

Masz zdecydowanie rację, raczej odpowiedź techniczną niż praktyczną. Możesz także odwrócić go, aby dodać znak po znaku do znaku, $resultdopóki nie dopasuje on pożądanej długości, ale jeśli pożądana długość jest dużą liczbą, jest ona równie nieefektywna.
konfetti

1
Możesz zacząć blisko właściwego miejsca, zaczynając od $desired_charsbajtów na dolnym końcu, a może 4*$desired_charsna górnym końcu. Ale nadal uważam, że najlepiej jest użyć czegoś zupełnie innego.
Peter Cordes,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.