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


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.