Como truncar o arquivo para o número máximo de caracteres (não bytes)

13

Como posso truncar um arquivo de texto (codificado em UTF-8) para determinado número de caracteres? Eu não me importo com os comprimentos de linha e o corte pode estar no meio da palavra.

  • cut parece operar em linhas, mas eu quero um arquivo inteiro.
  • head -c usa bytes, não caracteres.
por Pitel 31.07.2018 / 08:35

3 respostas

14

Alguns sistemas possuem um comando truncate que trunca arquivos para um número de bytes (não caracteres).

Eu não conheço nenhum que seja truncado para vários caracteres, embora você possa recorrer a perl , que é instalado por padrão na maioria dos sistemas:

perl

perl -Mopen=locale -ne '
  BEGIN{$/ = 34} truncate STDIN, tell STDIN; last' <> "$file"
  • Com -Mopen=locale , usamos a noção de localidade de quais caracteres são (portanto, em locales que usam o conjunto de caracteres UTF-8, são caracteres codificados em UTF-8). Substitua por -CS se quiser que a E / S seja decodificada / codificada em UTF-8, independentemente do conjunto de caracteres da localidade.

  • $/ = 34 : definimos o separador de registro como uma referência a um inteiro, que é uma forma de especificar registros de tamanho fixo (em número de caracteres ).

  • depois de ler o primeiro registro, truncamos stdin no lugar (assim no final do primeiro registro) e saímos.

sed GNU

Com o GNU sed , você poderia fazer (assumindo que o arquivo não contenha caracteres NUL ou seqüências de bytes que não formam caracteres válidos - ambos devem ser verdadeiros para arquivos de texto):

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

Mas isso é muito menos eficiente, pois lê o arquivo inteiro e armazena todo na memória, e escreve uma nova cópia.

GNU awk

Mesmo com o GNU awk :

awk -i inplace -v RS='^$' -e '{printf "%s", substr($0, 1, 1234)}' -E /dev/null "$file"
  • -e code -E /dev/null "$file" sendo uma maneira de passar nomes de arquivos arbitrários para gawk
  • RS='^$' : modo de suspensão .

Builtins de shell

Com ksh93 , bash ou zsh (com shells diferentes de zsh , supondo que o conteúdo não contenha bytes NUL):

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

com zsh :

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

Ou:

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

Com ksh93 ou bash (tenha cuidado com é falso para caracteres de múltiplos bytes em várias versões de bash ):

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

ksh93 também pode truncar o arquivo em vez de reescrevê-lo com o operador de redirecionamento <>; :

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

íconev + cabeça

Para imprimir os primeiros 1234 caracteres, outra opção poderia ser converter em uma codificação com um número fixo de bytes por caractere, como UTF32BE / UCS-4 :

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

head -c não é padrão, mas é bastante comum. Um equivalente padrão seria dd bs=1 count="$((1234 * 4))" , mas seria menos eficiente, pois leria a entrada e gravaria a saída um byte de cada vez¹. iconv é um comando padrão, mas os nomes de codificação não são padronizados, portanto, você pode encontrar sistemas sem UCS-4

Notas

Em qualquer caso, embora a saída tenha no máximo 1234 caracteres, pode acabar não sendo um texto válido, já que possivelmente terminaria em uma linha não delimitada.

Observe também que enquanto essas soluções não cortam texto no meio de um personagem, elas podem quebrá-lo no meio de um grapheme , como é expresso como U + 0065 U + 0301 (a e seguido de um sotaque agudo de combinação), ou grafemas sílabas de Hangul nas suas formas decompostas.

¹ e na entrada pipe você não pode usar bs valores além de 1 de forma confiável a menos que você use a extensão iflag=fullblock GNU, pois dd poderia fazer leituras curtas se ler o pipe mais rápido que iconv preenche

    
por 31.07.2018 / 09:23
5

Se você sabe que o arquivo de texto contém Unicode codificado como UTF-8, primeiro é necessário decodificar o UTF-8 para obter uma seqüência de entidades de caracteres Unicode e dividi-las.

Eu escolheria o Python 3.x para o trabalho.

Com o Python 3.x, a função open () tem um argumento extra de palavra-chave encoding= para a leitura de arquivos de texto . A descrição do método io.TextIOBase.read () parece promissora.

Então, usando o Python 3, ficaria assim:

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

Obviamente, uma ferramenta real adicionaria argumentos de linha de comando, tratamento de erros, etc.

Com o Python 2.x, você pode implementar seu próprio objeto semelhante a um arquivo e decodificar o arquivo de entrada linha por linha.

    
por 31.07.2018 / 09:01
0

Gostaria de adicionar outra abordagem. Provavelmente não o melhor desempenho, e muito mais, mas é fácil de entender:

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

Invoque-o com $ ./scriptname <desired chars> <input file> .

Isso remove o último caractere, um por um, até que a meta seja atingida, o que parece ser um mau desempenho, especialmente para arquivos maiores. Eu só queria apresentar isso como uma ideia para mostrar mais possibilidades.

    
por 31.07.2018 / 18:41