Dosyayı maksimum karakter sayısına (bayt değil) kesme


13

Bir (UTF-8 kodlu) metin dosyasını belirli sayıda karaktere nasıl kısaltabilirim? Satır uzunluklarını umursamıyorum ve kesim kelimenin ortasında olabilir.

  • cut satırlarda çalışıyor gibi görünüyor, ama tam bir dosya istiyorum.
  • head -c karakter değil bayt kullanır.

GNU uygulamasının cuthala çok baytlı karakterleri desteklemediğini unutmayın. Eğer öyleyse, yapabilirdin cut -zc-1234 | tr -d '\0'.
Stéphane Chazelas

Emojileri nasıl ele almak istersiniz? Bazıları bir karakterden daha fazla ... stackoverflow.com/questions/51502486/…
phuzi

2
Karakter nedir? bazı semboller birkaç kod noktası kullanır
Jasen

Yanıtlar:


14

Bazı sistemlerde truncatedosyaları birkaç bayta (karakterlere değil) kesen bir komut bulunur .

perlÇoğu sistemde varsayılan olarak yüklenen çare olabilir, ancak bir dizi karakter için kesilen herhangi bir bilmiyorum :

perl

perl -Mopen=locale -ne '
  BEGIN{$/ = \1234} truncate STDIN, tell STDIN; last' <> "$file"
  • İle -Mopen=locale, yerel ayarın hangi karakter olduğu fikrini kullanırız (UTF-8 karakter kümesini kullanan yerel ayarlarda UTF-8 kodlu karakterler kullanılır). -CSYerel ayarın karakter kümesine bakılmaksızın G / Ç'nin UTF-8'de kodunun çözülmesini / kodlanmasını istiyorsanız değiştirin .

  • $/ = \1234: kayıt ayırıcısını, sabit uzunluktaki ( karakter sayısı olarak ) kayıtları belirtmenin bir yolu olan bir tam sayı referansına ayarladık .

  • daha sonra ilk kaydı okuduktan sonra, stdin'i yerinde keseriz (böylece ilk kaydın sonunda) ve çıkarız.

GNU sed

GNU sedile şunları yapabilirsiniz (dosyanın NUL karakterleri veya geçerli karakterler oluşturmayan bayt dizileri içermediği varsayılarak - her ikisi de metin dosyaları için geçerli olmalıdır):

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

Ancak bu, dosyayı tam olarak okuduğu ve hafızasında sakladığı ve yeni bir kopya yazdığı için çok daha az verimlidir.

GNU awk

GNU ile aynı awk:

awk -i inplace -v RS='^$' -e '{printf "%s", substr($0, 1, 1234)}' -E /dev/null "$file"
  • -e code -E /dev/null "$file" rasgele dosya adlarını iletmenin bir yolu olmak gawk
  • RS='^$': slurp modu .

Kabuk yapıları

İle ksh93, bashya da zsh(dışındaki kabuklarla zshNUL içermeyen içeriği varsayarak bayt):

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

İle zsh:

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

Veya:

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

İle ksh93ya da bash(dikkat çeşitli sürümleri çoklu bayt karakterler için 's Bogusbash ):

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

ksh93ayrıca dosyayı yeniden <>;yönlendirme işleciyle yeniden yazmak yerine kısaltabilir :

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

iconv + kafa

To yazdırmak ilk 1234 karakter, başka bir seçenek gibi karakterin başına bayt sabit sayıda bir kodlama dönüştürmek olabilir UTF32BE/ UCS-4:

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

head -cstandart değildir, ancak oldukça yaygındır. Standart bir eşdeğer dd bs=1 count="$((1234 * 4))", girdiyi okuyacağı ve çıktıyı her seferinde bir bayt yazacağı için daha az etkili olacaktır, ancak daha az verimli olacaktır. iconvstandart bir komuttur ancak kodlama adları standartlaştırılmamıştır, bu nedenleUCS-4

notlar

Her durumda, çıktının en fazla 1234 karakteri olmasına rağmen, muhtemelen sınırlandırılmamış bir satırla biteceğinden geçerli metin olmayabilir.

Ayrıca, bu çözümler bir karakterin ortasında metin kesmezken , éU + 0065 U + 0301 ( eardından bir akut aksanı birleştiren) gibi ifade edilen bir grafiğin ortasında onu kırabileceklerini , veya Hangul heceli grafemleri ayrıştırılmış formlarında.


¹ ve boru girişinde bs, iflag=fullblockGNU uzantısını kullanmadığınız sürece 1'den farklı değerleri güvenilir şekilde kullanamazsınız , çünkü ddboruyu iconvdoldurmaktan daha hızlı okursa kısa okumalar yapabilir


yapabilirdidd bs=1234 count=4
Jasen

2
@Jasen, bu güvenilir olmaz. Bkz. Düzenleme.
Stéphane Chazelas

Vaov! yakın olması için kullanışlı olurdu! Çok sayıda kullanışlı Unix komutunu bildiğimi sanıyordum ama bu harika seçeneklerin inanılmaz bir listesi.
Mark Stewart

5

Metin dosyasının UTF-8 olarak kodlanmış Unicode içerdiğini biliyorsanız, önce Unicode karakter varlıkları dizisini almak ve bunları bölmek için önce UTF-8 kodunu çözmeniz gerekir.

İş için Python 3.x'i seçerdim.

Python 3.x ile open () fonksiyonunun metin dosyalarınıencoding= okumak için ekstra bir anahtar kelime argümanı vardır . İo.TextIOBase.read () yönteminin açıklaması umut vericidir.

Python 3'ü kullanarak şöyle görünecektir:

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

Açıkçası gerçek bir araç komut satırı argümanları, hata işleme vb.

Python 2.x ile kendi dosya benzeri nesnenizi uygulayabilir ve girdi dosyasının satır satır kodunu çözebilirsiniz.


Evet, yapabilirim. Ama bu CI derleme makineleri için, bu yüzden daha standart bir Linux komutu kullanmak istiyorum.
Pitel

5
"Standart Linux" Linux lezzetinizde ne anlama
geliyorsa

1
Aslında, Python, zaten bazı versiyonları, bu günlerde oldukça standart.
muru

Cevabımı, metin dosyalarını açıkça işleyebilen Python 3 için snippet ile zaten düzenledim.
Michael Ströder

0

Başka bir yaklaşım daha eklemek istiyorum. Muhtemelen en iyi performans akıllıca ve daha uzun değil, ama anlaşılması kolay:

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

İle çağır $ ./scriptname <desired chars> <input file>.

Bu, hedefe ulaşılana kadar son karakteri tek tek kaldırır, bu da özellikle büyük dosyalar için gerçekten kötü bir performans gibi görünüyor. Bunu daha fazla olasılık göstermek için bir fikir olarak sunmak istedim.


Evet, bu kesinlikle performans için korkunç. N uzunluğunda bir dosya wciçin, hedef noktanın dosyanın yarısı kadar O (n ^ 2) toplam bayt sırasına göre hesaplanır. Artırdığınız veya azalttığınız bir değişkeni veya benzeri bir echo -n "${result::-$chop}" | wc -mşeyi kullanarak doğrusal arama yerine ikili arama yapmak mümkün olmalıdır . (Ve dosyadayken, dosya içeriği başlasa -eveya bir şey kullanıyor olsa bile güvenli hale getirin printf). Ama yine de her bir giriş karakterine sadece bir kez bakan yöntemleri yenmeyeceksiniz, bu yüzden muhtemelen buna değmez.
Peter Cordes

Kesinlikle haklısın, pratik bir cevaptan ziyade teknik bir cevaptan daha fazlası. $resultİstenilen uzunlukla eşleşene kadar char ile char eklemek için tersine çevirebilirsiniz , ancak istenen uzunluk yüksek bir sayı ise, aynı verimsizdir.
konfeti

1
$desired_charsAlt uçtaki baytlardan veya belki 4*$desired_charsde yüksek uçtan başlayarak doğru yere yakın bir yerden başlayabilirsiniz . Ama yine de tamamen başka bir şey kullanmak en iyisi.
Peter Cordes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.