3. 文件的基本操作

对于文件,常见的操作包括创建、删除、修改、读、写等。关于各种操作对应的“背后动作”将在下一章 第六章 文件系统操作 详细分析。

范例:创建文件

socket 文件是一类特殊的文件,可以通过 C 语言创建,这里不做介绍(暂时不知道是否可以用命令直接创建),其他文件将通过命令创建。

$ touch regular_file      #创建普通文件
$ mkdir directory_file     #创建目录文件,目录文件里头可以包含更多文件
$ ln regular_file regular_file_hard_link  #硬链接,是原文件的一个完整拷比
$ ln -s regular_file regular_file_soft_link  #类似一个文件指针,指向原文件
$ mkfifo fifo_pipe   #或者通过 "mknod fifo_pipe p" 来创建,FIFO满足先进先出的特点
$ mknod hda1_block_dev_file b 3 1  #块设备
$ mknod null_char_dev_file c 1 3   #字符设备

创建一个文件实际上是在文件系统中添加了一个节点(inode),该节点信息将保存到文件系统的节点表中。更形象地说,就是在一颗树上长了一颗新的叶子(文件)或者枝条(目录文件,上面还可以长叶子的那种),这些可以通过tree命令或者ls 命令形象地呈现出来。文件系统从日常使用的角度,完全可以当成一颗倒立的树来看,因为它们太像了,太容易记忆啦。

$ tree 当前目录
或
$ ls 当前目录

范例:删除文件

删除文件最直接的印象是这个文件再也不存在了,这同样可以通过 ls 或者 tree 命令呈现出来,就像树木被砍掉一个分支或者摘掉一片叶子一样。实际上,这些文件删除之后,并不是立即消失了,而是仅仅做了删除标记,因此,如果删除之后,没有相关的磁盘写操作把相应的磁盘空间“覆盖”,那么原理上是可以恢复的(虽然如此,但是这样的工作往往很麻烦,所以在删除一些重要数据时,请务必三思而后行,比如做好备份工作),相应的做法可以参考后续资料。

具体删除文件的命令有 rm,如果要删除空目录,可以用 rmdir 命令。例如:

$ rm regular_file
$ rmdir directory_file
$ rm -r directory_file_not_empty

rm 有两个非常重要的参数,一个是 -f,这个命令是非常“野蛮的”,它估计给很多 Linux user 带来了痛苦,另外一个是 -i,这个命令是非常“温柔的”,它估计让很多用户感觉烦躁不已。用哪个还是根据您的“心情”吧,如果做好了充分的备份工作,或者采取了一些有效避免灾难性后果的动作的话,您在做这些工作的时候就可以放心一些啦。

范例:复制文件

文件的复制通常是指文件内容的“临时”复制。通过这一节开头的介绍,我们应该了解到,文件的硬链接和软链接在某种意义上说也是“文件的复制”,前者同步复制文件内容,后者在读写的情况下同步“复制”文件内容。例如:

  • cp 命令常规地复制文件(复制目录需要 -r 选项)
    $ cp regular_file regular_file_copy
    $ cp -r diretory_file directory_file_copy
    
  • 创建硬链接(linkcopy 不同之处是:link 为同步更新,copy 则不然,复制之后两者不再相关)
    $ ln regular_file regular_file_hard_link
    
  • 创建软链接
    $ ln -s regular_file regluar_file_soft_link
    

范例:修改文件名

修改文件名实际上仅仅修改了文件名标识符。可以通过 mv 命令来实现修改文件名操作(即重命名)。

$ mv regular_file regular_file_new_name

范例:编辑文件

编辑文件实际上是操作文件的内容,对应普通文本文件的编辑,这里主要涉及到文件内容的读、写、追加、删除等。这些工作通常会通过专门的编辑器来做,这类编辑器有命令行下的 vimemacs 和图形界面下的 gedit,kedit 等。如果是一些特定的文件,会有专门的编辑和处理工具,比如图像处理软件 gimp,文档编辑软件 OpenOffice 等。这些工具一般都会有专门的教程。

下面主要简单介绍 Linux 下通过重定向来实现文件的这些常规的编辑操作。

  • 创建一个文件并写入 abcde
    $ echo "abcde" > new_regular_file
    
  • 再往上面的文件中追加一行 abcde
    $ echo "abcde" >> new_regular_file
    
  • 按行读一个文件
    $ while read LINE; do echo $LINE; done < test.sh
    
  • 提示:如果要把包含重定向的字符串变量当作命令来执行,请使用 eval 命令,否则无法解释重定向。例如:
    $ redirect="echo \"abcde\" >test_redirect_file"
    $ $redirect   #这里会把>当作字符 > 打印出来,而不会当作 重定向 解释
    "abcde" >test_redirect_file
    $ eval $redirect    #这样才会把 > 解释成 重定向
    $ cat test_redirect_file
    abcde
    

范例:压缩/解压缩文件

压缩和解压缩文件在一定意义上来说是为了方便文件内容的传输,不过也可能有一些特定的用途,比如内核和文件系统的映像文件等(更多相关的知识请参考后续资料)。

这里仅介绍几种常见的压缩和解压缩方法:

  • tar
    $ tar -cf file.tar file   #压缩
    $ tar -xf file.tar    #解压
    
  • gz
    $ gzip  -9 file
    $ gunzip file
    
  • tar.gz
    $ tar -zcf file.tar.gz file
    $ tar -zxf file.tar.gz
    
  • bz2
    $ bzip2 file
    $ bunzip2 file
    
  • tar.bz2
    $ tar -jcf file.tar.bz2 file
    $ tar -jxf file.tar.bz2
    

通过上面的演示,应该已经非常清楚 tarbzip2,bunzip2,gzip,gunzip命令的角色了吧?如果还不清楚,多操作和比较一些上面的命令,并查看它们的手册:man tar`...

范例:文件搜索(文件定位)

文件搜索是指在某个目录层次中找出具有某些属性的文件在文件系统中的位置,这个位置如果扩展到整个网络,那么可以表示为一个 URL 地址,对于本地的地址,可以表示为 file://+ 本地路径。本地路径在 Linux 系统下是以 / 开头,例如,每个用户的家目录可以表示为: file:///home/ 。下面仅仅介绍本地文件搜索的一些办法。

find 命令提供了一种“及时的”搜索办法,它根据用户的请求,在指定的目录层次中遍历所有文件直到找到需要的文件为止。而 updatedb+locate 提供了一种“快速的”的搜索策略,updatedb 更新并产生一个本地文件数据库,而 locate 通过文件名检索这个数据库以便快速找到相应的文件。前者支持通过各种文件属性进行搜索,并且提供了一个接口(-exec 选项)用于处理搜索后的文件。因此为“单条命令”脚本的爱好者提供了极大的方便,不过对于根据文件名的搜索而言,updatedb+locate 的方式在搜索效率上会有明显提高。下面简单介绍这两种方法:

  • find 命令基本使用演示
    $ find ./ -name "*.c" -o -name "*.h"  #找出所有的C语言文件,-o是或者
    $ find ./ \( -name "*.c" -o -name "*.h" \) -exec mv '{}' ./c_files/ \;
    # 把找到的文件移到c_files下,这种用法非常有趣
    
  • 上面的用法可以用 xargs 命令替代
    $ find ./ -name "*.c" -o -name "*.h" | xargs -i mv '{}' ./c_files/
    # 如果要对文件做更复杂的操作,可以考虑把mv改写为你自己的处理命令,例如,我需要修
    
  • 改所有的文件名后缀为大写
    $ find ./ -name "*.c" -o -name "*.h" | xargs -i ./toupper.sh '{}' ./c_files/
    
  • toupper.sh 就是我们需要实现的转换小写为大写的一个处理文件,具体实现如下:
    $ cat toupper.sh
    #!/bin/bash
    # the {} will be expended to the current line and becomen the first argument of this script
    FROM=$1
    BASENAME=${FROM##*/}
    BASE=${BASENAME%.*}
    SUFFIX=${BASENAME##*.}
    TOSUFFIX="$(echo $SUFFIX | tr '[a-z]' '[A-Z]')"
    TO=$2/$BASE.$TOSUFFIX
    COM="mv $FROM $TO"
    echo $COM
    eval $COM
    
  • updatedb+locate 基本使用演示
    $ updatedb #更新库
    $ locate find*.gz #查找包含find字符串的所有gz压缩包
    
  • 实际上,除了上面两种命令外,Linux 下还有命令查找工具:whichwhereis,前者用于返回某个命令的全路径,而后者用于返回某个命令、源文件、man 文件的路径。例如,查找find命令的绝对路径:
    $ which find
    /usr/bin/find
    $ whereis find
    find: /usr/bin/find /usr/X11R6/bin/find /usr/bin/X11/find /usr/X11/bin/find /usr/man/man1/find.1.gz /usr/share/man/man1/find.1.gz /usr/X11/man/man1/find.1.gz
    

需要提到的是,如果想根据文件的内容搜索文件,那么 findupdatedb+locate 以及 whichwhereis 都无能为力啦,可选的方法是 grepsed 等命令,前者在加上 -r 参数以后可以在指定目录下文件中搜索指定的文件内容,后者再使用 -i 参数后,可以对文件内容进行替换。它们的基本用法在前面的章节中已经详细介绍了,这里就不再赘述。

值得强调的是,这些命令对文件的操作非常有意义。它们在某个程度上把文件系统结构给抽象了,使得对整个文件系统的操作简化为对单个文件的操作,而单个文件如果仅仅考虑文本部分,那么最终却转化成了之前的字符串操作,即上一节讨论过的内容。为了更清楚地了解文件的组织结构,文件之间的关系,在下一节将深入探讨文件系统。

参考资料