如何将文件截断为最大字符数(不是字节)


13

如何将(UTF-8编码的)文本文件截断为给定的字符数?我不在乎行长,剪切可以在单词中间。

  • cut 似乎可以在线运行,但是我需要一个完整的文件。
  • head -c 使用字节,而不是字符。

请注意,GNU的实现cut仍然不支持多字节字符。如果这样做,您可以做cut -zc-1234 | tr -d '\0'
斯特凡Chazelas

您想如何处理表情符号?有些
不仅仅是

2
什么角色 一些符号使用多个代码点,
Jasen

Answers:


14

某些系统具有truncate将文件截断为多个字节(不是字符)的命令。

我不知道会截断许多字符,尽管您可以perl使用大多数系统默认安装的字符:

佩尔

perl -Mopen=locale -ne '
  BEGIN{$/ = \1234} truncate STDIN, tell STDIN; last' <> "$file"
  • 使用-Mopen=locale,我们使用语言环境中的字符概念(因此,在使用UTF-8字符集的语言环境中,即UTF-8编码的字符)。-CS如果您希望以UTF-8格式对I / O进行解码/编码,而不考虑语言环境的字符集,请替换为。

  • $/ = \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='^$'lur饮模式

内建Shell

使用ksh93bashzshzsh假设内容不包含NUL字节,则使用非shell ):

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]}

使用ksh93bash(请注意,在多个版本中,多字节字符是虚假的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除非使用iflag=fullblockGNU扩展名,否则您不能可靠地使用非1的值,dd如果它读取管道的速度比iconv填充管道快,则可能会进行短读


可以做到dd bs=1234 count=4
Jasen

2
@Jasen,那将是不可靠的。参见编辑。
斯特凡Chazelas

哇!您在附近就很方便!我以为我知道很多方便的Unix命令,但这是令人难以置信的绝佳选择列表。
马克·斯图尔特

5

如果您知道文本文件包含以UTF-8编码的Unicode,则必须首先对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命令。
Pitel

5
无论“标准Linux”意味着您的Linux风格是什么...
MichaelStröder18年

1
确实,Python,无论如何还是它的某些版本,如今已经很标准了。
大师

我已经用Python 3的代码段编辑了答案,该代码段可以显式处理文本文件。
MichaelStröder18年

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逐个字符地添加到中,$result直到匹配所需的长度为止,但是如果所需的长度较大,则效率同样低下。
五彩纸屑

1
您可以从$desired_chars低端或高端开始以接近字节的位置开始4*$desired_chars。但是我仍然认为最好完全使用其他东西。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.