关于路径和通配符
Linux中分绝对路径和相对路径,绝对路径一定是从 / 开始写的,相对路径不从根开始写,还可能使用路径符号。
路径展开符号:
1 | . :(一个点)表示当前目录 |
切换路径用 cd 命令;
显示当前所在目录用 pwd 命令。若当前所在目录为链接目录,使用 pwd 显示的将是链接自身,使用 -P 选项将定位到链接的原始目录。
1 | [[email protected] ~]# ll ; cd tmp; pwd; pwd -P |
获取文件名使用 basename 命令,获取文件所在目录使用 dirname 命令。注意,这两个命令其实不太完善,它不会检查文件或目录是否存在,只要写出来了就会去获取。
1 | [[email protected] tmp]# basename /etc/shadow |
bash shell 通配符:
可以使用 “*”、”?”、”[]” 等的通配符来扩展路径或文件名。例如, ls *.log 将列出当前路径下所有以 “.log” 字符结尾的文件名 (但不包括 “.” 开头的隐藏文件)。
默认情况下,bash 提供的通配符规则比较弱,例如 “*” 无法匹配文件名开头的 “.”,无法匹配路径分隔符号 (即斜线 “/“),但可以通过 set 或 shopt 命令开启额外的通配功能,实现更完善的通配符规则。
例如,默认情况下,想要匹配目录 / path 下所有隐藏文件和非隐藏文件,如下:
1 | ls .* * |
开启 dotglob 功能,”*” 就可以匹配以 “.” 开头的文件:
1 | shopt -s dotglob |
有时想要递归到目录内部,又想要匹配文件名,例如想要递归找出多层目录 / path 下所有的 “.css” 文件,这时可以开启 globstar 功能,使用 “两星连珠”(**) 就可以匹配匹配路径斜线。
1 | shopt -s globstar # 开启星号匹配模式 |
必须要说明的是,对于非 bash 内置命令,有些可能也提供了自己的通配符匹配方式,它们的通配模式和 shell 提供的可能并不一样。例如 find 的 “-name” 选项就可以采用自己的通配符,它的星号 “*“ 可以匹配以点开头的隐藏文件,如 find /var/log -name "*.log"
。
查看目录内容 (ls和tree)
ls 命令列出目录中的内容,和 dir 命令完全等价。tree 命令按树状结构递归列出目录和子目录中的内容,而 ls 使用 -R 选项时才会递归列出。
注意:ls 的结果中是以制表符分隔多个文件的。
ls命令
ls 的各个选项说明如下:
1 | -l:(long) 长格式显示,即显示属性等信息 (包括 mtime)。注意:显示的目录大小是节点所占大小。像 win 一样计算目录大小时包括文件大小要用 du -sh |
注意,ls 以 -h 显示文件大小时,一般显示的都是不带 B 的单位,如 K/M/G,它们的转换比例是 1024,如果显示的都是带了 B 的,如 KB/MB/GB,则它们的转换比例为 1000 而非 1024,一般很少显示带 B 的大小。
不得不说,ls 本身不能显示出文件的全路径名是一大缺陷,不过好在使用 find 命令可以很简单的就获取到。
以下是使用 ls -l 显示文件长格式的属性。
1 | [[email protected] ~]# ll /tmp |
可以查出 7 列属性。
tree命令
有可能 tree 命令不存在,需要安装 tree 包才有 (安装:yum -y install tree
)。
tree 命令的选项说明如下:
1 | 【 匹配选项:】 |
下图是一个较全的输出结果。
文件的时间戳 (atime/ctime/mtime)
文件的时间属性有三种:atime/ctime/mtime。atime 是 access time,即上一次的访问时间;mtime 是 modify time,是文件的修改时间;ctime 是 change time,也是文件的修改时间,只不过这个修改时间计算的 inode 修改时间,也就是元数据修改时间。文件还有一个创建时间 (create time),大多数 unix 系统上都认为这是个无用的属性,一般工具无法获取这个时间,但是对于 ext 家族文件系统,通过它的底层调试工具 debugfs 可以获取 create time。
但 mtime 只有修改文件内容才会改变,更准确的说是修改了它的 data block 部分;而 ctime 是修改文件属性时改变的,确切的说是修改了它的元数据部分,例如重命名文件,修改文件所有者,移动文件 (移动文件没有改变 datablock,只是改变了其 inode 指针,或文件名) 等. 当然,修改文件内容也一定会改变 ctime(修改文件内容至少已经修改了 inode 记录上的 mtime,这也是元数据),也就是说 mtime 的改变一定会引起 ctime 的改变。
对目录而言,考虑目录文件的 data block,可知在目录中创建、删除文件以及目录内其他任意文件操作都会改变 mtime,因为目录里的任何东西都是目录中的内容;而目录的 ctime,除了目录的 mtime 引起 ctime 改变之外,对目录本身的元数据修改也会改变 ctime。
总结下:
- (1).atime只在文件被打开访问时才改变,若不是打开文件编辑内容(如重定向内容到文件中),则 ctime 和 mtime 的改变不会引起atime的改变;
- (2).mtime的改变一定引起ctime的改变,而访问文件时(例如cat),atime不一定会改变,所以atime”改变”(这个改变是假象,见下文分析)不一定会影响ctime。(见下面的relatime说明)
关于relatime
atime/ctime/mtime 是 Posix 标准要求操作系统维护的时间戳信息。但是每次将 atime、ctime 和 mtime 写入到硬盘中 (这些不会写入缓存,只要修改就是写入磁盘,即使从缓存读取文件内容也如此) 效率很低。有多低?下图是写 ctime 消耗的时间,几乎总要花费零点几秒。
mtime 要被修改,必然是修改了文件内容,这时候将 mtime 写入到硬盘中是应该的。但是 atime 和 ctime 呢?很多情况下根本用不到 atime 和 ctime,在频繁访问文件的时候,都要修改 atime 和 ctime,这样效率会降低很多很多,所以 mount 有个 noatime 选项来避免这种负面影响。
CentOS6 引入了一个新的 atime 维护机制 relatime:除非两次修改 atime 的时间超过 1 天 (默认设置 86400 秒),或者修改了 mtime,否则访问文件的 inode 不会引起 atime 的改变。换句话说,当 cat 一个文件的时候,它的 atime 可能会改变,但是你稍后再 cat,它不会再改变。
由于 cat 文件的时候 atime 可能不会改变,所以可能也就不会引起 ctime 的改变。
relatime维护的atime是可以控制的,详见man mount的relatime和redhat 官方手册
文件/目录的创建和删除
创建目录mkdir
1 | mkdir [-mp] 目录名 |
示例:
1 | 在tmp目录中创建一个test1目录 |
创建文件touch
1 | touch file_name |
示例:
1 | [[email protected] ~]# touch /tmp/test1/test1.txt |
多个 {} 还可以交换扩展。类似(a+b)(c+d)=ac+ad+bc+bd
。
1 | 创建 a_c、a_d、b_c、b_d 四个文件 |
touch 主要是修改文件的时间戳信息,当 touch 的文件不存在时就自动创建该文件。可以使用touch –c
来取消创建动作。
touch 可以更改最近一次访问时间 (atime),最近一次修改时间 (mtime),文件属性修改时间 (ctime),这些时间可以通过命令 stat file 来查看。其中 ctime 是文件属性上的更改,即元数据的更改,比如修改权限。
touch -a
修改 atime,-m 修改 mtime,没有修改 ctime 的选项。因为使用 touch 改变 atime 或 mtime,同时也都会改变 ctime,虽说 atime 并不总是会影响 ctime(如 cat 文件时)。
-t 选项表示使用 “[[CC]YY]MMDDhhmm[.ss]” 格式的时间替代当前时间。
1 | 将file的atime修改为2012年12月21号12点12分 |
-d选项表示使用指定的字符串描述时间格式来替代当前时间,如”3 days ago”,”next Sunday”等很多种格式。
所以,touch 命令选项说明如下:
1 | -c:强制不创建文件 |
删除文件/目录
1 | rm [-rfi] file_name |
1 | [[email protected] ~]# rm -rf /tmp/test2 |
删除空目录时还可以使用 rmdir。
在删除文件之前,一定一定要确定是否真的删除。最好使用rm -i
(默认已经在~/.bashrc 中定义了该别名),除非在脚本中,否则不要轻易使用 - f 选项。已经有非常多的人不小心rm -rf *
和rm -rf /NNNNN
了。例如想删除rm –rf /abc*
,结果习惯性的多敲了一个空格rm –rf /abc *
,完了。
查看文件类型 file 命令
这是一个简单查看文件类型的命令,查看文件是属于二进制文件还是数据文件还是 ASCII 文件。
1 | [[email protected] tmp]# file /etc/aliases.db |
除了基本的查看文件类型的功能外,file 还有一个 “-s” 选项,是一个超强力的选项,可以查看设备的文件系统类型。像有些分区工具如 parted 在分区时是可以指定文件系统的 (虽然不建议这么做,CentOS 7 的 parted 版本中已经取消了该功能),但在分区后格式化前,一般是比较难查看该分区的文件系统类型的,但使用 file 可以查看到。
1 | [[email protected] ~]# file -s /dev/sda1 |
文件/目录复制和移动
cp命令
1 | cp [-apdriulfs] src dest # 复制单文件或单目录 |
一般使用cp -a
即可,对于目录加上-r
选项即可。
注意,bash 内置命令在进行通配符匹配文件的时候,”*“、”?”、”[]” 是无法匹配到以 “.” 开头的文件的,所以 “*“ 不会匹配隐藏文件。要通配隐藏文件,使用 “.” 代替上述几种通配元字符即可,它能匹配除了 “.” 和 “..” 这两个特殊目录外的所有文件。它并非通配符,而是表示当前目录,显然直接复制目录,是可以将隐藏文件复制走的。
例如,复制 /etc/skel 目录下所有文件包括隐藏文件到 /tmp 目录下。
1 | cp -a /etc/skel/. /tmp |
如果有重复文件,则即使加上 -f 选项,也一样会交互式询问。解决方法可以是使用 “yes” 这个工具,它会不断的生成 y 字母直到进程被杀掉,当然也可以自行指定要生成的字符串。
1 | yes | cp -a /etc/skel/. /tmp |
scp命令和执行过程分析
scp 是基于 ssh 的安全拷贝命令 (security copy),它是从古老的远程复制命令 rcp 改变而来,实现的是在 host 与 host 之间的拷贝,可以是本地到远程的、本地到本地的,甚至可以远程到远程复制。注意,scp 可能会询问密码。
如果 scp 拷贝的源文件在目标位置上已经存在时 (文件同名),scp 会替换已存在目标文件中的内容,但保持其 inode 号。
如果 scp 拷贝的源文件在目标位置上不存在,则会在目标位置上创建一个空文件,然后将源文件中的内容填充进去。
之所以解释上面的两句,是为了理解 scp 的机制,scp 拷贝本质是只是填充内容的过程,它不会去修改目标文件的很多属性,对于从远程复制到另一远程时,其机制见后文。
1 | scp [-12BCpqrv] [-l limit] [-o ssh_option] [-P port] [[[email protected]]host1:]file1 ... [[[email protected]]host2:]file2 |
示例:
- 把本地文件 /home/a.tar.tz 拷贝到远程服务器 192.168.0.2 上的 /home/tmp,连接时使用远程的 root 用户
1 | scp /home/a.tar.tz [email protected]:/home/tmp/ |
- 目标主机不写路径时,表示拷贝到对方的家目录下
1 | scp /home/a.tar.tz [email protected] |
- 把远程文件 /home/a.tar.gz 拷贝到本机
1 | # 不接本地目录表示拷贝到当前目录 |
- 拷贝远程机器的 /home/ 目录到本地 /tmp 目录下
1 | scp -r [email protected]:/home/ /tmp |
- 从远程主机 192.168.100.60 拷贝文件到另一台远程主机 192.168.100.62 上
1 | scp [email protected]:/tmp/copy.txt [email protected]:/tmp |
在远程复制到远程的过程中,例如在本地执行 scp 命令将 A 主机 (192.168.100.60) 上的 /tmp/copy.txt 复制到 B 主机 (192.168.100.62) 上的 /tmp 目录下,如果使用 -v 选项查看调试信息的话,会发现它的步骤类似是这样的。
1 | # 以下是从结果中提取的过程 |
也就是说,远程主机 A 到远程主机 B 的复制,实际上是将 scp 命令行从本地传递到主机 A 上,由 A 自己去执行 scp 命令。也就是说,本地主机不会和主机 B 有任何交互行为,本地主机就像是一个代理执行者一样,只是帮助传送 scp 命令行以及帮助显示信息。
其实从本地主机和主机 A 上的~/.ssh/know_hosts
文件中可以看出,本地主机只是添加了主机 A 的信息,并没有添加主机 B 的信息,而在主机 A 上则添加了主机 B 的信息。
mv命令
mv命令移动文件和目录,还可以用于重命名文件或目录。
1 | mv [-iuf] src dest # 移动单个文件或目录 |
mv 默认已经是递归移动, 不需要 -r 参数。
mv的一个经典问题(mv的本质)
该问题涉及文件系统操作文件的机制,若不理解,请先深入学习文件系统。
mv不能实现里层同名目录覆盖外层同名目录。如 /tmp 下有 a 目录,a 目录里还有 a 目录,将不能实现/tmp/a/a 移动到/tmp。
1 | [[email protected] tmp]# tree -L 3 a -fC |
1 | [[email protected] tmp]# mv -f /tmp/a/* /tmp |
要解释为何会如此,先说明移动和覆盖动作的本质。
同文件系统下移动文件实际上是修改目标文件所在目录的 data block,向其中添加一行指向 inode table 中待移动文件的 inode 的指针,如果目标路径下有同名文件,则会提示是否覆盖,实际上是覆盖指向该同名文件的 inode 指针,由于同名文件的 inode 记录指针被覆盖,就无法再找到该文件的 data block,所以该文件被标记为删除。
跨文件系统移动文件的本质:如果目标路径下没有同名文件,则先为此文件分配一个 inode 号,并在目标目录的 data block 中添加一条指向该 inode 号的新记录 (是全新的),然后将文件复制到目标位置,复制成功则删除源文件,复制失败则保留源文件;如果目标路径下有同名文件,则提示是否要覆盖,如果选择覆盖,则将该同名文件的 inode 指针指向新分配的 inode 号,然后将文件复制到目标位置,复制成功则删除源文件,复制失败则保留源文件。
也就是说,同文件系统下移动文件时,inode 记录不变 (如 inode 号),当然,时间戳是一定会改变的,因为移动过程中修改了 inode 指向 data block 的指针。而跨文件系统下移动文件时,inode 记录完全改变,它是新添加的记录。
再考虑上面的问题,同文件系统下移动文件时先在目标位置 /tmp 的 data block 中添加一条记录,如果同名则提示覆盖,覆盖时会先删除 /tmp 的 data block 中的 a 对应的记录,再添加将要移动文件的记录。从上面的结果也可以看出是先提示覆盖再提示目录非空的错误。
设想下,如果 /tmp/a/a 移动到 /tmp 下并重命名为 b,则其动作是直接向 /tmp 的 data block 中添加 b 的记录,如果此时正好 /tmp 下已有 b 目录,则先删除 /tmp 的 data block 中 b 目录对应的记录,再添加移动后的 b 记录。
但是现在不是重命名为 b,而是覆盖 /tmp/a,此时的动作按原理应该是先提示是否覆盖,如果是,则删除 /tmp 的 data block 中 a 对应的记录,但由于此时 /tmp/a 目录中还有文件,该记录无法删除 (因为如果要删除了该记录,代表删除了 /tmp/a 整个目录,而删除整个 /tmp/a 目录需要删除里面所有的文件,在删除它们之前的一个动作是把 /tmp/a 中的所有目录和文件的 inode 号标记为未使用,但此刻要移动的源目录 /tmp/a/a 是在使用当中的),所以提示目录非空而无法删除,这里所指的非空目录指的是 /tmp/a,而非是 /tmp/a/a 非空。
但是在 Windows 操作系统下,里层目录是可以直接覆盖外层同名目录的,这和文件系统的行为有关。
其实在这个问题中,可以看出 mv 的很多原理。
查看文件内容
cat 命令
输出一个或多个文件的内容。
1 | cat [OPTION]... [FILE]... |
cat 还有一个重要功能,允许将分行键入的内容输入到一个文件中去。
首先测试<<eof
,这表示将键入的内容追加到标准输入 stdin 中 (不是从标准输入中读取), eof 可以随便使用其他符号代替。
1 | [[email protected] tmp]# cat <<eof |
再测试< eof
,发现没有输入的机会,并且此时只能使用 eof 作为符号,EOF 或其他任何都不可以。因为<eof
是读取标准输入,会将 eof 当成输入文件处理。所以一定要使用<<eof
,这表示 here document,而两个 eof 正是 document 的起始和结束标志。
1 | [[email protected] tmp]# cat <eof |
1 | [[email protected] tmp]# cat <eox |
再进一步测试<<eof
的功能,将键入的内容重定向到文件而非标准输入中。这时有两种书写方案:
第一种方案:>>filename<<eof
或>filename<<eof
1 | [[email protected] ~]# cat >>/tmp/test.txt<<EOF # 输入到这里按回车键继续输入下一行 |
第二种方案:<<eof>filename
或<<eof>>filename
1 | [[email protected] tmp]# cat <<eof>log.txt |
两种方案结果是一样的,且总是使用<<eof
,只不过所写的位置不同而已,不管写在哪个位置,它都表示将键入的内容追加到标准输入。然后再使用>filename
或>>filename
控制重定向的方式,将标准输入中的内容重定向到 filename 文件中。
tac
tac 和 cat 字母正好是相反的,其作用也是和 cat 相反的,它会反向输出行,将最后一行放在第一行的位置输出,依此类推。但是,tac 没有显示行号的参数。
1 | echo -e '1\n2\n3\n4\n5' | tac |
head
head 打印前面的几行。
1 | head [-n num] | [-num] [-v] filename |
-n num
是显示文件的前 num 行,num 可以是+/-
或不加正负号的整数,如果是正整数或不写 + 号,则显示前 num 行。如果是负整数,则从后向前数 num 行,并打印除了这些行的前面所有的行,即打印除了最后 num 行的所有行,也即总行数减 num 的前正数行。不写 -n 时默认是前 10 行。正整数时-n num
可以直接简写-num
。
不管怎么样,它取的都是前几行,哪怕是负整数也是前几行。
示例:
1 | 取出默认前10行,但总共才有5行 |
或者
1 | # 取出前2行 |
tail
tail 和 head 相反,是显示后面的行,默认是后 10 行。
1 | tail [OPTION]... [FILE]... |
-n -num
或-num
或-n num
(num 为正整数) 表示输出最后的 num 行。使用-n +num
(num 为正整数) 则表示输出从第 num 行开始的所有行。
1 | 等价于 tail -n 3和tail -n -3 |
tail 还有一个重要的参数 -f,监控文件的内容变化。当一个用户不断修改某个文件的尾部,另一个用户就可以通过这个命令来刷新并显示这些修改后的内容。
nl
以行号的方式查看内容。
常用-b a
,表示不论是否空行都显示行号,等价于cat -n
。不写选项时,默认-b t
,表示空行不显示行号,等价于cat -b
。
1 | 默认空行不显示行号 |
more 和 less
按页显示文件内容。使用 more 时,使用/
搜索字符串,按下 n 或 N 键表示向下或向上继续搜索。使用 less 时,还多了一个搜索功能,使用?
搜索字符串,同样,使用 n 或 N 键可以向上或向下继续搜索。
比较文件内容
1 | diff file1 file2 |
文件查找类命令
搜索文件的路径在何处以及文件的名称为何。
which
显示命令或脚本的全路径,默认也会将命令的别名显示出来。
1 | which mv |
whereis
找出二进制文件、源文件和 man 文档文件。
1 | whereis cd |
whatis
列出给定命令 (并非一定是命令) 的 man 文档信息。
1 | whatis passwd |
根据上面的结果,执行:
1 | man 1 passwd # 获取passwd命令的man文档 |
locate
没什么好说的。
find
内容太多,使用两篇单独的文章解释