Shell 内置函数

Shell 内置函数

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

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

答案1

有些系统有一个truncate命令可以将文件截断为多个字节(不是字符)。

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

珀尔

perl -Mopen=locale -ne '
  BEGIN{$/ = \1234} truncate STDIN, tell STDIN; last' <> "$file"
  • 对于-Mopen=locale,我们使用区域设置的字符概念(因此在使用 UTF-8 字符集的区域设置中,即 UTF-8 编码的字符)。-CS如果您希望 I/O 以 UTF-8 解码/编码,无论区域设置的字符集如何,请替换为。

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

  • 然后在读取第一条记录后,我们就地截断标准输入(因此在第一条记录的末尾)并退出。

GNU sed

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

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

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

GNU awk

与 GNU 相同awk

awk -i /usr/share/awk/inplace.awk -v RS='^$' -e '
  {printf "%s", substr($0, 1, 1234)}' -E /dev/null "$file"
  • -e code -E /dev/null "$file"是将任意文件名传递给的一种方法gawk
  • RS='^$'吸食模式

不使用-i inplaceas尝试首先从当前工作目录gawk加载inplace扩展(asinplace或),有人可能已经在其中植入了恶意软件。随系统提供的扩展inplace.awk的路径可能会有所不同,请参阅输出inplacegawkgawk 'BEGIN{print ENVIRON["AWKPATH"]}'

Shell 内置函数

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

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"

图标 + 头

打印对于前 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(ae后跟组合锐音重音),或分解形式的韩文音节字素。


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

答案2

如果您知道文本文件包含编码为 UTF-8 的 Unicode,则必须首先解码 UTF-8 以获取 Unicode 字符实体序列并拆分它们。

我会选择 Python 3.x 来完成这项工作。

使用 Python 3.x 的函数打开()有一个额外的关键字参数encoding=供阅读文本文件。方法描述io.TextIOBase.read()看起来很有希望。

所以使用 Python 3 看起来像这样:

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

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

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

答案3

使用(以前称为 Perl6)

Raku 提供对 Unicode 的高级内置支持。除了文件句柄名称之外,字素都转换为 Unicode 联盟的“标准化形式 C“ (NFC) 默认情况下。下面是表情符号的示例。

输入示例:

~$ raku -e 'for (0..8) -> $i { $_.[0..$i].join.put given "\x1F600".."\x1F64F"};' > emoticons_0-to-8.txt
~$ cat emoticons_0-to-8.txt

答案4

我想添加另一种方法。可能不是最好的性能,而且更长,但很容易理解:

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

这会一一删除最后一个字符,直到达到目标,这似乎在性能方面非常糟糕,尤其是对于较大的文件。我只是想把这个作为一个想法来展示,以展示更多的可能性。

相关内容