Zsh开发指南(第⼗四篇⽂件读写)
导读
之前我们也偶尔接触过读写⽂件的⽅法,本篇会系统讲读写⽂件的各种⽅法。
写⽂件
写⽂件要⽐读⽂件简单⼀些,最常⽤的⽤法是使⽤ > 直接将命令的输出重定向到⽂件。如果⽂件存在,内容会被覆盖;如果⽂件不存在,会被创建。
% echo abc >
如果不想覆盖之前的⽂件内容,可以追加写⼊:
% echo abc >>
这样如果⽂件存在,内容会被追加写⼊进去;如果⽂件不存在,也会被创建。
创建⽂件
有时我们只想先创建个⽂件,等以后需要的时候再写⼊。
touch 命令⽤于创建⽂件(普通⽂件):
%
# 或者⽤ echo 输出重定向,效果和 touch ⼀样
# 加 -n 是因为不加的话 echo 会输出⼀个换⾏符
% echo -n >& >&
# 或者使⽤输⼊重定向
% >& >& </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 &
# 使⽤输⼊重定向
% & </dev/null
# 也可以使⽤ truncate 命令清空⽂件
% truncate -s
通常使⽤第⼀种⽅法即可,⽐较简单易懂。⾮特殊场景尽量不要⽤像 truncate 这样不常见的命令。
删除⽂件
删除⽂件的⽅法⽐较单⼀,⽤ rm 命令即可。
%
# -f 参数代表即使⽂件不存在也不报错
% rm -
# -r 参数可以递归删除⽬录和⽂件
% rm -r dir1 dir2 test*.txt
# -v 参数代表 rm 会输出删除⽂件的过程
% rm -v test*.txt
removed ''
removed ''
删除⽂件必须借助 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 >
可以直接把数组写⼊到⽂件,每⾏⼀个元素:
% array=(aa bb cc)
% print -l $array >
如果是将⼀段内容⽐较固定的字符串写⼊到⽂件,可以这样:
# 在脚本中也是如此,第⼆⾏以后的⾏⾸ > 代表换⾏,⾮输⼊内容
# <<EOF 代表遇到 EOF 时会终⽌输⼊内容
# ⾥边也可以使⽤变量
% > <<EOF
> aa
> bb
> cc dd
> ee
> EOF
%
aa
bb
cc dd
ee
⽤ mapfile 读写⽂件
如果不喜欢使⽤重定向符号,还可以⽤哈希表来操作⽂件。Zsh 有⼀个 zsh/mapfile 模块,⽤起来很⽅便:% zmodload zsh/mapfile
# 这样就可以创建⽂件并写⼊内容,如果⽂件存在则会被覆盖
% ]="ab cd"
%
ab cd
# 判断⽂件是否存在
% (($+])) && echo good
good
# 读取⽂件
% echo $]
ab cd
# 删除⽂件
% unset "]"
# 遍历⽂件
% for i (${(k)mapfile}) {
> echo $i
> }
<
<
从⽂件中间位置写⼊
有时我们需要从⼀个⽂件的中间位置(⽐如从第 100 的字符或者第三⾏开始)继续写⼊,覆盖之后的内容。Zsh 并不直接提供这样的⽅法,但我们可以迂回实现,先⽤ truncate 命令把⽂件截断,然后追加写。如果⽂件后边的内容还需要保留,可以在截断之前先读取进来(见下⽂读⽂件部分的例⼦),最后再写回去。
% echo 1234567890 >
# 只保留前 5 个字符
% truncate -s
%
12345
% echo abcde >>
%
12345abcde
读⽂件
读取整个⽂件
读取整个⽂件⽐较容易:
% str=$(&)
% echo $str
aa
bb
cc dd
ee
按⾏遍历⽂件
如果⽂件⽐较⼤,那读取整个⽂件会消耗很多资源,可以按⾏遍历⽂件内容:
linux怎么读文件内容% while {read i} {
> echo $i
> } &
aa
bb
cc dd
ee
read 命令是从标准输⼊读取⼀⾏内容,把标准输⼊重定向后,就变成了从⽂件读取。
读取指定⾏
如果只需要读取指定的某⾏或者某些⾏,不需要⽤上边的⽅法加⾃⼰计数。
# (f)2 是读取第⼆⾏
% echo ${"$(&)"[(f)2]}
bb
读取⽂件到数组
读取⽂件内容到数组中,每⾏是数组的⼀个元素:
% array=(${(f)"$(&)"})
读取指定数量的字符
有时我们需要按字节数来读取⽂件内容,⽽不是按⾏读取。
%
1234567890
# -k5 是只最多读取 5 个字节,-u 0 是从 fd 0 读取,不然会卡住
% read -k 5 -u 0 str &
% echo $str
12345
向⽂件中间插⼊内容
有时我们会遇到⽐较⿇烦的场景,在某个⽂件中间插⼊⼀些内容,⽽前后的内容保持不变。
Zsh 并没有直接提供这样的功能,但我们可以迂回实现。
% echo -n 1234567890 >
# 先全部读进来
% str=$(&)
# 截断⽂件
% truncate -s
# 插⼊内容
% echo -n abcde >>
# 将后半部分⽂件追加回去
% echo -n $str[6,-1] >>
%
12345abcde67890
但如果⽐较⽐较⼤的话,就不能将整个⽂件全部读进来,可以先在循环⾥⽤ read -k num ⼀次读固定数量的字符,然后写⼊⼀个中间⽂件,然后再 truncate 原⽂件,插⼊内容。最后再 cat 中间⽂件 >> 原⽂件 追加原来的后半部分内容即可。
另外这种从⽂件中间写⼊或者读取内容的场景,都可以使⽤ dd 命令实现,可以⾃⾏搜索 dd 命令的⽤法。
总结
本⽂⽐较详细地介绍了各种读写⽂件的⽅法,基本可以覆盖常⽤的场景。
本⽂不再更新,全系列⽂章在此更新维护:
付费解决 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等领域相关问题,灵活定价,欢迎咨询,
ly50247。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。