Zsh 开发指南(第十四篇 文件读写)

导读

之前我们也偶尔接触过读写文件的方法,本篇会系统讲读写文件的各种方法。

写文件

写文件要比读文件简单一些,最常用的用法是使用 > 直接将命令的输出重定向到文件。如果文件存在,内容会被覆盖;如果文件不存在,会被创建。

% echo abc > test.txt

如果不想覆盖之前的文件内容,可以追加写入:

% echo abc >> test.txt

这样如果文件存在,内容会被追加写入进去;如果文件不存在,也会被创建。

创建文件

有时我们只想先创建个文件,等以后需要的时候再写入。

touch 命令用于创建文件(普通文件):

% touch test1.txt test2.txt

# 或者用 echo 输出重定向,效果和 touch 一样
# 加 -n 是因为不加的话 echo 会输出一个换行符
% echo -n >>test1.txt >>test2.txt

# 或者使用输入重定向
% >>test1.txt >>test2.txt </dev/null

# mkdir 用来创建目录,如果需要在新目录创建文件
% mkdir dir1 dir2

如果文件已经存在,touch 命令会更新它的时间(mtime、ctime、atime 一起更新,其余两种方法不会)到当前时间。另外下边的清空文件方法,也都可以用来创建文件。touch 命令的使用比较方便,但如果想尽量少依赖外部命令,可以使用后两种方法。

因为文件创建过程通常不存在性能瓶颈,不用过多考虑性能因素。如果需要创建大量文件,可以在自己的环境分别用这几种方法试验几次,看需要多少时间。

我在树莓派 3B 简单测试一下:

# 三个脚本,分别创建 1000 个文件
% cat test1 test2 test3
#!/bin/zsh

touch test1{1..1000}.txt
#!/bin/zsh

echo -n >>test2{1..1000}.txt
#!/bin/zsh

>>test3{1..1000}.txt </dev/null
# 运行了几次,结果差不多
% time ./test1; time ./test2; time ./test3
./test1  0.02s user 0.03s system 86% cpu 0.058 total
./test2  0.02s user 0.02s system 70% cpu 0.056 total
./test3  0.03s user 0.01s system 72% cpu 0.055 total

另外如果文件数量太多的话,方法二、三要按批次创建,因为一个进程能打开的 fd 总数是有上限的。

清空文件

有时我们需要清空一个现有的文件:

# 使用 echo 输出重定向
% echo -n >test.txt

# 使用输入重定向
% >test.txt </dev/null

# 也可以使用 truncate 命令清空文件
% truncate -s 0 test.txt

通常使用第一种方法即可,比较简单易懂。非特殊场景尽量不要用像 truncate 这样不常见的命令。

删除文件

删除文件的方法比较单一,用 rm 命令即可。

% rm test1.txt test2.txt

# -f 参数代表即使文件不存在也不报错
% rm -f test1.txt test2.txt

# -r 参数可以递归删除目录和文件
% rm -r dir1 dir2 test*.txt

# -v 参数代表 rm 会输出删除文件的过程
% rm -v test*.txt
removed 'test1.txt'
removed 'test2.txt'

删除文件必须借助 rm 命令。如果一定要不依赖外部命令的话,zsh/files 模块里也有一个 rm 命令,可以用 zmodload zsh/files 加载,然后 rm 就变成了内部命令,用法基本相同。

% zmodload zsh/files
% which -a rm
rm: shell built-in command
/usr/bin/rm

此外 zsh/files 中还有内置的 chgrp、chown、ln、mkdir、mv、rmdir、sync 命令。如果不想依赖外部命令,或者系统环境出问题了用不了外部命令,可以使用这些。这可以作为命令不存在或者因为命令本身问题执行异常的一个 fallback 方案,来提高脚本的健壮性。

多行文本写入

通常我们写文件时不会每一行都单独写入,这样效率太低。

可以先把字符串拼接起来,然后一次性写入,这样比多次写入效率更高:

% str=ab
% str+="\ncd"
% str +="\n$str"

echo $str > test.txt

可以直接把数组写入到文件,每行一个元素:

% array=(aa bb cc)

% print -l $array > test.txt

如果是将一段内容比较固定的字符串写入到文件,可以这样:

# 在脚本中也是如此,第二行以后的行首 > 代表换行,非输入内容
# <<EOF 代表遇到 EOF 时会终止输入内容
# 里边也可以使用变量
% > test.txt <<EOF
> aa
> bb
> cc dd
> ee
> EOF

% cat test.txt
aa
bb
cc dd
ee

用 mapfile 读写文件

如果不喜欢使用重定向符号,还可以用哈希表来操作文件。Zsh 有一个 zsh/mapfile 模块,用起来很方便:

% zmodload zsh/mapfile

# 这样就可以创建文件并写入内容,如果文件存在则会被覆盖
% mapfile[test.txt]="ab cd"
% cat test.txt
ab cd

# 判断文件是否存在
% (($+mapfile[test.txt])) && echo good
good

# 读取文件
% echo $mapfile[test.txt]
ab cd

# 删除文件
% unset "mapfile[test.txt]"

# 遍历文件
% for i (${(k)mapfile}) {
> echo $i
> }
test1.txt
test2.txt

从文件中间位置写入

有时我们需要从一个文件的中间位置(比如从第 100 的字符或者第三行开始)继续写入,覆盖之后的内容。Zsh 并不直接提供这样的方法,但我们可以迂回实现,先用 truncate 命令把文件截断,然后追加写。如果文件后边的内容还需要保留,可以在截断之前先读取进来(见下文读文件部分的例子),最后再写回去。

% echo 1234567890 > test.txt
# 只保留前 5 个字符
% truncate -s 5 test.txt
% cat test.txt
12345 
% echo abcde >> test.txt
% cat test.txt
12345abcde

读文件

读取整个文件

读取整个文件比较容易:

% str=$(<test.txt)
% echo $str
aa
bb
cc dd
ee

按行遍历文件

如果文件比较大,那读取整个文件会消耗很多资源,可以按行遍历文件内容:

% while {read i} {
> echo $i
> } <test.txt
aa
bb
cc dd
ee

read 命令是从标准输入读取一行内容,把标准输入重定向后,就变成了从文件读取。

读取指定行

如果只需要读取指定的某行或者某些行,不需要用上边的方法加自己计数。

# (f)2 是读取第二行
% echo ${"$(<test.txt)"[(f)2]}
bb

读取文件到数组

读取文件内容到数组中,每行是数组的一个元素:

% array=(${(f)"$(<test.txt)"})

读取指定数量的字符

有时我们需要按字节数来读取文件内容,而不是按行读取。

% cat test.txt
1234567890
# -k5 是只最多读取 5 个字节,-u 0 是从 fd 0 读取,不然会卡住
% read -k 5 -u 0 str <test.txt
% echo $str
12345

向文件中间插入内容

有时我们会遇到比较麻烦的场景,在某个文件中间插入一些内容,而前后的内容保持不变。

Zsh 并没有直接提供这样的功能,但我们可以迂回实现。

% echo -n 1234567890 > test.txt
# 先全部读进来
% str=$(<test.txt)
# 截断文件
% truncate -s 5 test.txt
# 插入内容
% echo -n abcde >> test.txt
# 将后半部分文件追加回去
% echo -n $str[6,-1] >> test.txt
% cat test.txt
12345abcde67890

但如果比较比较大的话,就不能将整个文件全部读进来,可以先在循环里用 read -k num 一次读固定数量的字符,然后写入一个中间文件,然后再 truncate 原文件,插入内容。最后再 cat 中间文件 >> 原文件 追加原来的后半部分内容即可。

另外这种从文件中间写入或者读取内容的场景,都可以使用 dd 命令实现,可以自行搜索 dd 命令的用法。

总结

本文比较详细地介绍了各种读写文件的方法,基本可以覆盖常用的场景。

本文不再更新,全系列文章在此更新维护:github.com/goreliu/zshguide

付费解决 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等领域相关问题,灵活定价,欢迎咨询,微信 ly50247。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容