Як скоротити файл до максимальної кількості символів (не байтів)


13

Як я можу врізати текстовий файл (закодований UTF-8) до заданої кількості символів? Мене не хвилює довжина рядків, і розріз може бути в середині слова.

  • cut здається, працює на лініях, але я хочу цілий файл.
  • head -c використовує байти, а не символи.

Зауважте, що реалізація GNU cutдосі не підтримує багатобайтові символи. Якби це сталося, ти міг би зробити cut -zc-1234 | tr -d '\0'.
— Стефан Шазелас

Як ви хочете обробити емоджи? Деякі з них більш , що один персонаж ... stackoverflow.com/questions/51502486 / ...
— phuzi

2
Що за персонаж? деякі символи використовують декілька кодових точок,
— Ясен

Відповіді:


14

У деяких системах є 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

З GNU sedви могли б зробити це (якщо припустимо, що файл не містить символів NUL або послідовностей байтів, які не утворюють дійсних символів - обидва повинні бути правдивими для текстових файлів):

sed -Ez -i -- 's/^(.{1234}).*/\1/' "$file"

Але це набагато менш ефективно, оскільки він читає файл у повному обсязі та зберігає його цілком у пам'яті та пише нову копію.

GNU awk

Те саме з GNU awk:

awk -i inplace -v RS='^$' -e '{printf "%s", substr($0, 1, 1234)}' -E /dev/null "$file"
  • -e code -E /dev/null "$file" є одним із способів передавати довільні імена файлів gawk
  • RS='^$': режим зригування .

Shell вбудовані

Із 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"

iconv + голова

Для друку перших 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
— Ясен

2
@Jasen, це не було б надійним. Див. Редагування.
— Стефан Шазелас

Оце Так! вам було б зручно мати поруч! Я думав, що знаю багато зручних команд Unix, але це неймовірний список чудових варіантів.
— Марк Стюарт

5

Якщо ви знаєте, що текстовий файл містить 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 ви можете реалізувати власний об’єкт, подібний до файлів, та декодувати вхідний файл по черзі.


Так, я міг би це зробити. Але це для машин для побудови CI, тому я хотів би більш раціонально використовувати якусь стандартну команду Linux.
— Пітел

5
Що б не означав "стандартний Linux" на ваш смак Linux ...
— Майкл Стрьодер

1
Дійсно, Python, якась версія його так чи інакше, є досить стандартною в наші дні.
— муру

Я вже відредагував свою відповідь фрагментом для Python 3, який може явно обробляти текстові файли.
— Майкл Стрьодер

0

Я хотів би додати ще один підхід. Напевно, не найкраща ефективність і набагато довше, але її легко зрозуміти:

#!/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>.

Це видаляє останнє знаряддя одне за одним до тих пір, поки мета не буде досягнута, що здається справді поганою ефективністю, особливо для великих файлів. Я просто хотів представити це як ідею, щоб показати більше можливостей.


Так, це безумовно жахливо для виконання. Для файлу довжиною n wcрозраховується на порядок загальної кількості байтів O (n ^ 2) для цільової точки на півдорозі у файл. Потрібно мати можливість двійкового пошуку замість лінійного пошуку, використовуючи змінну, яку ви збільшуєте чи зменшуєте, як- echo -n "${result::-$chop}" | wc -mнебудь чи щось. (І поки ви на цьому перебуваєте, зробіть це безпечним, навіть якщо вміст файлу починається -eабо щось, можливо, використовується printf). Але ви все одно не будете перемагати методи, які дивляться лише на кожен символ введення один раз, тому, напевно, не варто.
— Пітер Кордес

Ви, безумовно, праві, швидше технічна відповідь, а не практична відповідь. Ви також можете змінити його, щоб додати знак char до char до тих $resultпір, поки він не відповідає бажаній довжині, але якщо бажана довжина є великою, це так само неефективно.
— конфетті

1
Ви можете почати близько до потрібного місця, починаючи з $desired_charsбайтів у нижньому кінці, а може, і 4*$desired_charsу верхньому. Але все ж я думаю, що найкраще використовувати щось інше цілком.
— Пітер Кордес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.