Cara memotong file ke jumlah karakter maksimum (bukan byte)


13

Bagaimana saya bisa memotong file teks (UTF-8) yang dikodekan ke jumlah karakter yang diberikan? Saya tidak peduli tentang panjang garis dan potongannya bisa di tengah kata.

  • cut tampaknya beroperasi pada baris, tetapi saya ingin seluruh file.
  • head -c menggunakan byte, bukan karakter.

Perhatikan bahwa implementasi GNU cutmasih tidak mendukung karakter multi-byte. Jika ya, Anda bisa melakukannya cut -zc-1234 | tr -d '\0'.
Stéphane Chazelas

Bagaimana Anda ingin menangani emoji? Beberapa lebih dari satu karakter ... stackoverflow.com/questions/51502486/…
phuzi

2
Apa itu karakter? beberapa simbol menggunakan beberapa titik kode,
Jasen

Jawaban:


14

Beberapa sistem memiliki truncateperintah yang memotong file ke sejumlah byte (bukan karakter).

Saya tidak tahu ada yang memotong ke sejumlah karakter, meskipun Anda dapat menggunakan perlyang diinstal secara default pada kebanyakan sistem:

perl

perl -Mopen=locale -ne '
  BEGIN{$/ = \1234} truncate STDIN, tell STDIN; last' <> "$file"
  • Dengan -Mopen=locale, kami menggunakan gagasan lokal tentang karakter apa (jadi di lokal menggunakan charset UTF-8, itu karakter yang dikodekan UTF-8). Ganti dengan -CSjika Anda ingin I / O didekodekan / disandikan dalam UTF-8 terlepas dari charset lokal.

  • $/ = \1234: kami mengatur pemisah rekaman ke referensi ke integer yang merupakan cara untuk menentukan rekaman dengan panjang tetap (dalam jumlah karakter ).

  • kemudian setelah membaca catatan pertama, kita memotong stdin di tempat (jadi di akhir catatan pertama) dan keluar.

GNU sed

Dengan GNU sed, Anda bisa melakukannya (dengan asumsi file tidak mengandung karakter NUL atau urutan byte yang tidak membentuk karakter yang valid - keduanya harus benar dari file teks):

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

Tapi itu jauh kurang efisien, karena membaca file secara penuh dan menyimpannya dalam memori, dan menulis salinan baru.

GNU awk

Sama dengan GNU awk:

awk -i inplace -v RS='^$' -e '{printf "%s", substr($0, 1, 1234)}' -E /dev/null "$file"
  • -e code -E /dev/null "$file" menjadi salah satu cara untuk memberikan nama file yang sewenang-wenang kepada gawk
  • RS='^$': mode slurp .

Shell bawaan

Dengan ksh93, bashatau zsh(dengan cangkang selain zsh, dengan asumsi konten tidak mengandung byte NUL):

content=$(cat < "$file" && echo .) &&
  content=${content%.} &&
  printf %s "${content:0:1234}" > "$file"

Dengan zsh:

read -k1234 -u0 s < $file &&
  printf %s $s > $file

Atau:

zmodload zsh/mapfile
mapfile[$file]=${mapfile[$file][1,1234]}

Dengan ksh93atau bash(waspadalah itu palsu untuk karakter multi-byte dalam beberapa versibash ):

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

ksh93juga dapat memotong file di tempat alih-alih menulis ulang dengan <>;operator pengalihan:

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

iconv + head

Untuk mencetak 1234 karakter pertama, opsi lain adalah mengonversi ke pengkodean dengan jumlah byte tetap per karakter seperti UTF32BE/ UCS-4:

iconv -t UCS-4 < "$file" | head -c "$((1234 * 4))" | iconv -f UCS-4

head -ctidak standar, tetapi cukup umum. Setara standar akan dd bs=1 count="$((1234 * 4))"tetapi akan kurang efisien, karena akan membaca input dan menulis output satu byte pada suatu waktu¹. iconvadalah perintah standar tetapi nama penyandian tidak terstandarisasi, sehingga Anda mungkin menemukan sistem tanpaUCS-4

Catatan

Bagaimanapun, meskipun output akan memiliki paling banyak 1.234 karakter, itu mungkin berakhir menjadi teks yang tidak valid, karena mungkin akan berakhir pada baris yang tidak dibatasi.

Perhatikan juga bahwa walaupun solusi tersebut tidak memotong teks di tengah karakter, mereka dapat memecahnya di tengah grapheme , seperti yang édinyatakan sebagai U + 0065 U + 0301 ( ediikuti oleh kombinasi aksen akut), atau grapheme suku kata Hangul dalam bentuk terurai.


¹ dan pada input pipa Anda tidak dapat menggunakan bsnilai selain 1 secara andal kecuali Anda menggunakan iflag=fullblockekstensi GNU, seperti ddhalnya bacaan singkat jika membaca pipa lebih cepat daripada iconvmengisinya


bisa lakukandd bs=1234 count=4
Jasen

2
@ Yasen, itu tidak bisa diandalkan. Lihat edit.
Stéphane Chazelas

Wow! Anda akan berguna untuk memiliki di dekatnya! Saya pikir saya tahu banyak perintah Unix yang berguna, tetapi ini adalah daftar pilihan yang luar biasa.
Mark Stewart

5

Jika Anda tahu bahwa file teks berisi Unicode dikodekan sebagai UTF-8 Anda harus terlebih dahulu mendekode UTF-8 untuk mendapatkan urutan entitas karakter Unicode dan membaginya.

Saya akan memilih Python 3.x untuk pekerjaan itu.

Dengan Python 3.x fungsi open () memiliki argumen kata kunci tambahan encoding=untuk membaca file teks . Deskripsi metode io.TextIOBase.read () terlihat menjanjikan.

Jadi menggunakan Python 3 akan terlihat seperti ini:

truncated = open('/path/to/file.txt', 'rt', encoding='utf-8').read(1000)

Jelas alat nyata akan menambahkan argumen baris perintah, penanganan kesalahan dll.

Dengan Python 2.x Anda bisa mengimplementasikan objek seperti file Anda sendiri dan mendekode file input baris demi baris.


Ya, saya bisa melakukan itu. Tapi ini untuk mesin CI build, jadi saya ingin menggunakan perintah Linux standar.
Pitel

5
Apa pun "Linux standar" artinya pada citarasa Linux Anda ...
Michael Ströder

1
Memang, Python, beberapa versi itu, cukup standar akhir-akhir ini.
muru

Saya sudah mengedit jawaban saya dengan snippet untuk Python 3 yang secara eksplisit dapat memproses file teks.
Michael Ströder

0

Saya ingin menambahkan pendekatan lain. Mungkin bukan kinerja terbaik yang bijaksana, dan lebih lama, tetapi mudah dimengerti:

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

Meminta dengan $ ./scriptname <desired chars> <input file>.

Ini menghapus karakter terakhir satu per satu hingga tujuan tercapai, yang tampaknya sangat buruk dalam kinerja terutama untuk file yang lebih besar. Saya hanya ingin menyajikan ini sebagai ide untuk menunjukkan lebih banyak kemungkinan.


Ya, ini jelas mengerikan untuk kinerja. Untuk file dengan panjang n, wcmengandalkan urutan total byte O (n ^ 2) untuk titik target setengah jalan ke file. Seharusnya dimungkinkan untuk pencarian biner alih-alih pencarian linear dengan menggunakan variabel yang Anda tambah atau perkecil, sukai echo -n "${result::-$chop}" | wc -matau sesuatu. (Dan saat Anda melakukannya, buatlah aman bahkan jika konten file dimulai dengan -eatau sesuatu, mungkin menggunakan printf). Tetapi Anda masih tidak akan mengalahkan metode yang hanya melihat setiap karakter input sekali, jadi mungkin tidak sepadan.
Peter Cordes

Anda pasti benar, lebih merupakan jawaban teknis daripada jawaban praktis. Anda juga bisa membalikkannya untuk menambahkan char by char ke $resulthingga cocok dengan panjang yang diinginkan, tetapi jika panjang yang diinginkan adalah angka yang tinggi itu juga tidak efisien.
confetti

1
Anda bisa mulai dekat dengan tempat yang tepat dengan memulai dengan $desired_charsbyte di ujung bawah, atau mungkin 4*$desired_charsdi ujung atas. Tapi tetap saja saya pikir lebih baik menggunakan sesuatu yang lain sama sekali.
Peter Cordes
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.