Як я можу врізати текстовий файл (закодований UTF-8) до заданої кількості символів? Мене не хвилює довжина рядків, і розріз може бути в середині слова.
cutздається, працює на лініях, але я хочу цілий файл.head -cвикористовує байти, а не символи.
Як я можу врізати текстовий файл (закодований UTF-8) до заданої кількості символів? Мене не хвилює довжина рядків, і розріз може бути в середині слова.
cut здається, працює на лініях, але я хочу цілий файл.head -c використовує байти, а не символи.Відповіді:
У деяких системах є truncateкоманда, яка виконує скорочення файлів до кількох байтів (а не символів).
Я не знаю жодного, який прирізається до кількох символів, хоча ви можете вдатися до того, perlщо встановлено за замовчуванням у більшості систем:
perl -Mopen=locale -ne '
BEGIN{$/ = \1234} truncate STDIN, tell STDIN; last' <> "$file"
З -Mopen=locale, ми використовуємо поняття локалі про те, що символи (тобто в локалях, що використовують схему UTF-8, це кодовані символи UTF-8). Замініть на те, -CSякщо ви хочете, щоб введення-вивід був розшифрований / закодований в UTF-8, незалежно від набору локалів.
$/ = \1234: ми встановлюємо роздільник записів на посилання на ціле число, яке є способом визначення записів фіксованої довжини (у кількості символів ).
потім, прочитавши перший запис, ми усікаємо stdin на місці (так в кінці першого запису) і виходимо.
З GNU sedви могли б зробити це (якщо припустимо, що файл не містить символів NUL або послідовностей байтів, які не утворюють дійсних символів - обидва повинні бути правдивими для текстових файлів):
sed -Ez -i -- 's/^(.{1234}).*/\1/' "$file"
Але це набагато менш ефективно, оскільки він читає файл у повному обсязі та зберігає його цілком у пам'яті та пише нову копію.
Те саме з GNU awk:
awk -i inplace -v RS='^$' -e '{printf "%s", substr($0, 1, 1234)}' -E /dev/null "$file"
-e code -E /dev/null "$file" є одним із способів передавати довільні імена файлів gawkRS='^$': режим зригування .Із ksh93( bashабо zshз оболонками, відмінними від того zsh, якщо вміст не містить байтів NUL):
content=$(cat < "$file" && echo .) &&
content=${content%.} &&
printf %s "${content:0:1234}" > "$file"
З zsh:
read -k1234 -u0 s < $file &&
printf %s $s > $file
Або:
zmodload zsh/mapfile
mapfile[$file]=${mapfile[$file][1,1234]}
З ksh93або bash(будьте обережні, що це багатобайтні символи в декількох версіяхbash ):
IFS= read -rN1234 s < "$file" &&
printf %s "$s" > "$file"
ksh93також може усікати файл замість того, щоб переписати його разом із його <>;оператором перенаправлення:
IFS= read -rN1234 0<>; "$file"
Для друку перших 1234 символів іншим варіантом може бути перетворення в кодування з фіксованою кількістю байтів на символ, як UTF32BE/ UCS-4:
iconv -t UCS-4 < "$file" | head -c "$((1234 * 4))" | iconv -f UCS-4
head -cне є стандартним, але досить поширеним. Стандартний еквівалент був би, dd bs=1 count="$((1234 * 4))"але був би менш ефективним, оскільки читав би вхід і записував вихід один байт за один раз¹. iconv- це стандартна команда, але імена кодування не стандартизовані, тому ви можете знайти системи безUCS-4
У будь-якому випадку, хоча вихід має містити не більше 1234 символів, він може виявитися неправдивим текстом, оскільки, можливо, він закінчиться в неограниченому рядку.
Також зауважте, що, хоча ці рішення не вирізають текст у середині символу, вони можуть порушити його посередині графеми , як éвираження як U + 0065 U + 0301 (з eподальшим поєднанням гострого акценту), або грануми складів Хангул у їх розкладених формах.
¹ і на вході в трубу ви не можете bsнадійно використовувати значення, крім 1, якщо ви не використовуєте iflag=fullblockрозширення GNU, як це ddможе зробити короткі зчитування, якщо він зчитує трубу швидше, ніж iconvзаповнює
dd bs=1234 count=4
Якщо ви знаєте, що текстовий файл містить Unicode, кодований як UTF-8, вам слід спершу розшифрувати UTF-8, щоб отримати послідовність утворень символів Unicode та розділити їх.
Я вибрав би Python 3.x для роботи.
З Python 3.x функція open () має додатковий аргумент ключового слова encoding=для читання текстових файлів . Опис методу io.TextIOBase.read () виглядає багатообіцяючим.
Отже, використовуючи Python 3, це виглядатиме так:
truncated = open('/path/to/file.txt', 'rt', encoding='utf-8').read(1000)
Очевидно, справжній інструмент додасть аргументи командного рядка, обробку помилок тощо.
За допомогою Python 2.x ви можете реалізувати власний об’єкт, подібний до файлів, та декодувати вхідний файл по черзі.
Я хотів би додати ще один підхід. Напевно, не найкраща ефективність і набагато довше, але її легко зрозуміти:
#!/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"
Викликайте його с $ ./scriptname <desired chars> <input file>.
Це видаляє останнє знаряддя одне за одним до тих пір, поки мета не буде досягнута, що здається справді поганою ефективністю, особливо для великих файлів. Я просто хотів представити це як ідею, щоб показати більше можливостей.
wcрозраховується на порядок загальної кількості байтів O (n ^ 2) для цільової точки на півдорозі у файл. Потрібно мати можливість двійкового пошуку замість лінійного пошуку, використовуючи змінну, яку ви збільшуєте чи зменшуєте, як- echo -n "${result::-$chop}" | wc -mнебудь чи щось. (І поки ви на цьому перебуваєте, зробіть це безпечним, навіть якщо вміст файлу починається -eабо щось, можливо, використовується printf). Але ви все одно не будете перемагати методи, які дивляться лише на кожен символ введення один раз, тому, напевно, не варто.
$resultпір, поки він не відповідає бажаній довжині, але якщо бажана довжина є великою, це так само неефективно.
$desired_charsбайтів у нижньому кінці, а може, і 4*$desired_charsу верхньому. Але все ж я думаю, що найкраще використовувати щось інше цілком.
cutдосі не підтримує багатобайтові символи. Якби це сталося, ти міг би зробитиcut -zc-1234 | tr -d '\0'.