Cách cắt bớt tệp thành số ký tự tối đa (không phải byte)


13

Làm cách nào tôi có thể cắt một tệp văn bản (được mã hóa UTF-8) thành số lượng ký tự đã cho? Tôi không quan tâm đến độ dài của dòng và phần cắt có thể ở giữa từ.

  • cut dường như hoạt động trên các dòng, nhưng tôi muốn cả một tập tin.
  • head -c sử dụng byte, không phải ký tự.

Lưu ý rằng việc triển khai GNU cutvẫn không hỗ trợ các ký tự nhiều byte. Nếu nó đã làm, bạn có thể làm cut -zc-1234 | tr -d '\0'.
Stéphane Chazelas

Bạn muốn xử lý biểu tượng cảm xúc như thế nào? Một số nhiều hơn một nhân vật ... stackoverflow.com/questions/51502486/ từ
phuzi

2
Nhân vật là gì? một số biểu tượng sử dụng một số điểm mã,
Jasen

Câu trả lời:


14

Một số hệ thống có truncatelệnh cắt các tệp thành một số byte (không phải ký tự).

Tôi không biết bất kỳ đoạn nào cắt ngắn một số ký tự, mặc dù bạn có thể sử perldụng mặc định được cài đặt trên hầu hết các hệ thống:

perl

perl -Mopen=locale -ne '
  BEGIN{$/ = \1234} truncate STDIN, tell STDIN; last' <> "$file"
  • Với -Mopen=locale, chúng tôi sử dụng khái niệm của địa phương về các ký tự là gì (vì vậy trong các địa phương sử dụng bộ ký tự UTF-8, đó là các ký tự được mã hóa UTF-8). Thay thế bằng -CSnếu bạn muốn I / O được giải mã / mã hóa trong UTF-8 bất kể bộ ký tự của miền địa phương.

  • $/ = \1234: chúng tôi đặt dấu tách bản ghi thành tham chiếu đến một số nguyên là cách chỉ định các bản ghi có độ dài cố định (theo số lượng ký tự ).

  • sau đó khi đọc bản ghi đầu tiên, chúng tôi cắt ngắn stdin tại chỗ (vì vậy ở phần cuối của bản ghi đầu tiên) và thoát.

GNU sed

Với GNU sed, bạn có thể làm (giả sử tệp không chứa các ký tự NUL hoặc chuỗi byte không tạo thành các ký tự hợp lệ - cả hai đều đúng với tệp văn bản):

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

Nhưng điều đó kém hiệu quả hơn nhiều, vì nó đọc toàn bộ tệp và lưu trữ toàn bộ trong bộ nhớ và viết một bản sao mới.

GNU awk

Tương tự với GNU awk:

awk -i inplace -v RS='^$' -e '{printf "%s", substr($0, 1, 1234)}' -E /dev/null "$file"
  • -e code -E /dev/null "$file" là một cách để chuyển tên tệp tùy ý đến gawk
  • RS='^$': chế độ bùn .

Vỏ tích hợp

Với ksh93, bashhoặc zsh(với các shell khác zsh, giả sử nội dung không chứa NUL byte):

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

Với zsh:

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

Hoặc là:

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

Với ksh93hoặc bash(hãy cẩn thận, nó không có thật cho các ký tự nhiều byte trong một số phiên bảnbash ):

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

ksh93cũng có thể cắt tệp tại chỗ thay vì viết lại bằng <>;toán tử chuyển hướng của nó :

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

iconv + đầu

Để in 1234 ký tự đầu tiên, một tùy chọn khác có thể là chuyển đổi thành mã hóa với số byte cố định trên mỗi ký tự như UTF32BE/ UCS-4:

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

head -ckhông chuẩn, nhưng khá phổ biến. Một tương đương tiêu chuẩn sẽ có dd bs=1 count="$((1234 * 4))"nhưng sẽ kém hiệu quả hơn, vì nó sẽ đọc đầu vào và ghi đầu ra một byte mỗi lần¹. iconvlà một lệnh tiêu chuẩn nhưng tên mã hóa không được tiêu chuẩn hóa, vì vậy bạn có thể tìm thấy các hệ thống mà không cóUCS-4

Ghi chú

Trong mọi trường hợp, mặc dù đầu ra sẽ có tối đa 1234 ký tự, nhưng cuối cùng nó có thể không phải là văn bản hợp lệ, vì nó có thể kết thúc bằng một dòng không phân cách.

Cũng lưu ý rằng mặc dù các giải pháp đó sẽ không cắt văn bản ở giữa một ký tự, nhưng chúng có thể phá vỡ nó ở giữa biểu đồ , giống như được éthể hiện dưới dạng U + 0065 U + 0301 ( etiếp theo là dấu trọng âm kết hợp), hoặc đồ thị âm tiết Hangul trong các hình thức phân tách của chúng.


Và trên đầu vào đường ống, bạn không thể sử dụng bscác giá trị ngoài 1 một cách đáng tin cậy trừ khi bạn sử dụng iflag=fullblockphần mở rộng GNU, cũng như ddcó thể đọc ngắn nếu nó đọc ống nhanh hơn iconvlấp đầy nó


có thể làmdd bs=1234 count=4
Jasen

2
@Jasen, điều đó sẽ không đáng tin cậy. Xem chỉnh sửa.
Stéphane Chazelas

Ồ bạn sẽ có ích để có gần đó! Tôi nghĩ rằng tôi biết rất nhiều lệnh Unix tiện dụng nhưng đây là một danh sách đáng kinh ngạc của các tùy chọn tuyệt vời.
Mark Stewart

5

Nếu bạn biết rằng tệp văn bản chứa Unicode được mã hóa dưới dạng UTF-8, trước tiên bạn phải giải mã UTF-8 để có được một chuỗi các thực thể ký tự Unicode và phân tách chúng.

Tôi sẽ chọn Python 3.x cho công việc.

Với Python 3.x, hàm open () có thêm đối số từ khóa encoding=để đọc tệp văn bản . Mô tả về phương thức io.TextIOBase.read () có vẻ đầy hứa hẹn.

Vì vậy, sử dụng Python 3 nó sẽ trông như thế này:

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

Rõ ràng một công cụ thực sự sẽ thêm các đối số dòng lệnh, xử lý lỗi, v.v.

Với Python 2.x, bạn có thể triển khai đối tượng giống như tệp của riêng mình và giải mã từng dòng tệp đầu vào.


Vâng, tôi có thể làm điều đó. Nhưng đó là cho các máy xây dựng CI, vì vậy tôi muốn sử dụng một số lệnh Linux tiêu chuẩn.
Pitel

5
Dù "Linux chuẩn" nghĩa là gì với hương vị Linux của bạn ...
Michael Ströder

1
Thật vậy, Python, một số phiên bản của nó dù sao, cũng khá chuẩn trong những ngày này.
muru

Tôi đã chỉnh sửa câu trả lời của mình với đoạn mã cho Python 3 có thể xử lý rõ ràng các tệp văn bản.
Michael Ströder

0

Tôi muốn thêm một cách tiếp cận khác. Có lẽ không phải là hiệu suất tốt nhất khôn ngoan, và lâu hơn, nhưng dễ hiểu:

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

Gọi nó với $ ./scriptname <desired chars> <input file>.

Điều này loại bỏ từng char cuối cùng cho đến khi đạt được mục tiêu, có vẻ như hiệu suất thực sự kém khôn ngoan đặc biệt là đối với các tệp lớn hơn. Tôi chỉ muốn trình bày điều này như một ý tưởng để thể hiện nhiều khả năng hơn.


Vâng, điều này chắc chắn là khủng khiếp cho hiệu suất. Đối với một tệp có độ dài n, wctính theo thứ tự tổng số byte (n ^ 2) cho một điểm đích ở nửa tệp vào tệp. Có thể tìm kiếm nhị phân thay vì tìm kiếm tuyến tính bằng cách sử dụng một biến mà bạn tăng hoặc giảm, như echo -n "${result::-$chop}" | wc -mhoặc một cái gì đó. (Và trong khi bạn đang ở đó, hãy làm cho nó an toàn ngay cả khi nội dung tệp bắt đầu bằng -ehoặc một cái gì đó, có thể sử dụng printf). Nhưng bạn vẫn sẽ không đánh bại các phương thức chỉ nhìn vào mỗi ký tự đầu vào một lần, vì vậy có lẽ không đáng.
Peter Cordes

Bạn chắc chắn đúng, nhiều hơn một câu trả lời kỹ thuật hơn là một câu trả lời thực tế. Bạn cũng có thể đảo ngược nó để thêm char bằng char $resultcho đến khi nó khớp với độ dài mong muốn, nhưng nếu độ dài mong muốn là một số cao thì nó cũng không hiệu quả.
confetti

1
Bạn có thể bắt đầu gần đúng nơi bằng cách bắt đầu bằng $desired_charsbyte ở cấp thấp hoặc có thể 4*$desired_charsở cấp cao. Nhưng tôi vẫn nghĩ tốt nhất là sử dụng thứ khác hoàn toàn.
Peter Cordes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.