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

Pit*_*tel 13 text-processing

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

  • cut 似乎在线操作,但我想要一个完整的文件。
  • head -c 使用字节,而不是字符。

Sté*_*las 14

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

我不知道任何截断为多个字符的字符,尽管您可以求助于perl大多数系统上默认安装的字符:

perl

perl -Mopen=locale -ne '
  BEGIN{$/ = \1234} truncate STDIN, tell STDIN; last' <> "$file"
Run Code Online (Sandbox Code Playgroud)
  • 使用-Mopen=locale,我们使用语言环境的字符是什么概念(因此在使用 UTF-8 字符集的语言环境中,这是 UTF-8 编码的字符)。-CS如果您希望 I/O 以 UTF-8 进行解码/编码,而不管区域设置的字符集如何,请替换为。

  • $/ = \1234:我们将记录分隔符设置为对整数的引用,这是一种指定固定长度(字符数)记录的方法。

  • 然后在读取第一条记录时,我们就地截断 stdin(所以在第一条记录的末尾)并退出。

GNU sed

使用 GNU sed,您可以这样做(假设文件不包含 NUL 字符或不构成有效字符的字节序列——这两者都应该适用于文本文件):

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

但这效率要低得多,因为它会完整读取文件并将其整个存储在内存中,然后写入一个新副本。

GNU awk

与 GNU 相同awk

awk -i inplace -v RS='^$' -e '{printf "%s", substr($0, 1, 1234)}' -E /dev/null "$file"
Run Code Online (Sandbox Code Playgroud)
  • -e code -E /dev/null "$file" 是将任意文件名传递给的一种方式 gawk
  • RS='^$':啜饮模式

Shell 内置函数

使用ksh93,bashzsh(使用除 之外的外壳zsh,假设内容不包含 NUL 字节):

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

zsh

read -k1234 -u0 s < $file &&
  printf %s $s > $file
Run Code Online (Sandbox Code Playgroud)

或者:

zmodload zsh/mapfile
mapfile[$file]=${mapfile[$file][1,1234]}
Run Code Online (Sandbox Code Playgroud)

使用ksh93or bash(当心它在多个版本的多字节字符中是伪造的bash):

IFS= read -rN1234 s < "$file" &&
  printf %s "$s" > "$file"
Run Code Online (Sandbox Code Playgroud)

ksh93也可以原地截断文件,而不是用它的<>;重定向操作符重写它:

IFS= read -rN1234 0<>; "$file"
Run Code Online (Sandbox Code Playgroud)

iconv + 头

打印前 1234 个字符,另一种选择是转换为每个字符具有固定字节数的编码,例如UTF32BE/ UCS-4

iconv -t UCS-4 < "$file" | head -c "$((1234 * 4))" | iconv -f UCS-4
Run Code Online (Sandbox Code Playgroud)

head -c不是标准的,但相当普遍。标准等价物将是dd bs=1 count="$((1234 * 4))"但效率较低,因为它会读取输入并一次写入一个字节¹。iconv是标准命令,但编码名称未标准化,因此您可能会发现系统没有UCS-4

笔记

在任何情况下,虽然输出最多有 1234 个字符,但它最终可能不是有效文本,因为它可能以非分隔行结尾。

同时还要注意,而这些解决方案将不能在字符的中间切开文字,他们就能把它在中间字形,像一个é表示为U + 0065 U + 0301(一个e接着一个组合重音符)或分解形式的韩文音节字素。


¹ 并且在管道输入上,bs除非您使用iflag=fullblockGNU 扩展,否则您不能可靠地使用 1 以外的值,因为dd如果读取管道比iconv填充管道快,则可以进行短读取

  • @Jasen,那是不可靠的。见编辑。 (2认同)

Mic*_*der 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)
Run Code Online (Sandbox Code Playgroud)

显然,真正的工具会添加命令行参数、错误处理等。

使用 Python 2.x,您可以实现自己的类文件对象并逐行解码输入文件。

  • 无论“标准 Linux”在您的 Linux 风格中意味着什么…… (5认同)