美文网首页Linux 相关文章程序员
Zsh 开发指南(第十四篇 文件读写)

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

作者: 陌辞寒 | 来源:发表于2017-08-31 09:52 被阅读245次

    导读

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

    写文件

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

    % 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。

    相关文章

      网友评论

        本文标题:Zsh 开发指南(第十四篇 文件读写)

        本文链接:https://www.haomeiwen.com/subject/gkgxjxtx.html