你打开终端,敲下 ls,回车。屏幕上跳出一串文件名。
这一来一回花掉的时间不到一秒,但中间至少穿过了三层东西:Shell 把你的输入拆成命令和参数,内核替你执行真正的系统调用,驱动再去问磁盘要数据。你看到的每一个字符,都是这条链路往返一趟的结果。
命令行难以上手,往往是因为中间这层结构不可见。下面把 Linux 最基础的那批指令过一遍,同时说明它们为什么是这样设计的;从系统本身的由来开始,一直到讲到权限模型。
1. Linux 的由来
要讲 Linux,得先讲 UNIX。Linux 的大量设计都继承自它:一切皆文件、目录树、用一堆小工具拼出复杂功能。知道这些从哪儿来,后面的行为才讲得通。
1.1 从 Multics 到 UNIX
1968 年,通用电气、贝尔实验室和麻省理工学院的研究人员合作开发了一个叫 Multics 的操作系统。它在多任务文件管理和用户连接上综合了很多新概念,但工程上过于庞大。
1969 到 1970 年,AT&T 贝尔实验室的 Ken Thompson 和 Dennis Ritchie 在借鉴 Multics 诸多特点的基础上,开发出了 UNIX。它跑在小型机上,满足了科研环境对系统的要求。从一开始,UNIX 就是一个高效的、多用户多任务的操作系统。
1972 年,贝尔实验室开始发放商业版本。使用者之一是加州大学伯克莱分校的计算机系,伯克莱给系统加了许多新特性,这些特性后来成了标准。1975 年,伯克莱由其下属部门 BSD 发行了自己的 UNIX 版本,成为 AT&T 版本的主要竞争者。
1.2 分裂与标准化
版本一多,麻烦就来了:软件开发商不知道自己的程序该跑在哪个版本上合适。到 80 年代中期,出现了两个互相竞争的标准------AT&T 的 System V 和 BSD。
AT&T 为此成立了 UNIX 系统实验室,专门综合不同版本、集中开发标准系统。1991 年,该实验室综合 System V Release 3、SUN OS 和 Xenix 的特点,发行了 System V Release 4。IBM 和惠普等公司则组成了 Open Software Foundation(OSF)与之抗衡,于是市场上并存两个标准商业版本。
1993 年,AT&T 把 UNIX 转卖给 Novell,UNIX 系统实验室成为 Novell 的一部分。SUN 公司把 System V Release 4 融进了自己的 SUN OS,发行了 Solaris。原本互相竞争的两种图形界面(Motif 和 Openlook)也合并成了通用平台环境(CDE)。
1.3 Linux 的出现
1991 年 10 月 5 日,赫尔辛基大学的研究生 Linus Benedict Torvalds 在一个 Usenet 新闻组里宣布,他写出了一个类 UNIX 的小操作系统,取名 Linux。它受到另一个小型 UNIX 系统 Minix 的启发,而 Minix 出自 Andrew S. Tanenbaum 之手。
有个细节常被记错:Torvalds 当天发布的并不是 0.01 版,而是 0.02 版。真正的 0.01 从未公开发布过,因为它不实用,Torvalds 只在第一个 FTP 站点上提供过它的源代码。0.02 版已经能跑 GNU bash 和 gcc,以及为数不多的其他语言。
此后版本从 0.02 到 0.03,又跳到 0.10,随着世界各地程序员陆续加入,很快到了 0.95。正式的 1.0 版本在 1994 年公布。
1.4 严格来说,Linux 只是内核
这里要分清一件事:Linux 本身只是操作系统内核。加上用户空间的应用程序之后,才构成一个完整的 Linux 操作系统。
这正好解释了准确的说法:讲内核的时候说「Linux 内核」,讲完整系统的时候才说「Linux」。
GNU 通用公共许可协议(GNU General Public License,GPL)在这里起了关键作用。它最初由 Richard Stallman 为 GNU 计划撰写,赋予了计算机程序「自由软件」的定义:任何基于 GPL 软件开发衍生的产品,发布时必须采用 GPL 许可证,且必须公开源代码。
正因如此,任何个人和机构都可以自由使用、修改和再发布 Linux 的底层源代码。Linux 也由此成为开源软件最有名的代表。
1.5 发行版:内核之外的整套打包
既然内核加应用才是完整系统,那么「谁来打包」就成了分歧点。常见的发行版大致这样分:
| 发行版 | 血统与定位 |
|---|---|
| Debian | 极其稳定,适合服务器;对新手不算友好,更适合系统管理员和高级用户 |
| Ubuntu | Debian 的衍生版,最受欢迎的免费系统之一;新手友好,服务器和云计算上都常见 |
| RHEL | 第一款面向商业市场的发行版,支持 x86 与 x86_64 等多种架构 |
| CentOS | 用 RHEL 的免费源代码重新构建,去掉了商标;用 YUM 管理软件包 |
| Fedora | 红帽的测试平台,产品成为企业级发行版之前在这里开发测试 |
| Kali Linux | Debian 衍生版,面向渗透测试,随带大量现成安全工具,用 APT 管理软件包 |
今天 Linux 的势力范围早已不限于服务器。移动端的 Android 就构建在 Linux 内核之上;路由器、电视、电子游戏机里也大量运行着定制版 Linux。
2. Shell:用户与内核之间的翻译层
严格说,Linux 指的是那个叫「内核(kernel)」的核心。但普通用户不能直接操作内核,中间隔着一层「外壳」程序,也就是 Shell。
Shell 最简单的定义是命令行解释器(command interpreter),它只做两件事:
- 把用户的命令翻译给内核处理
- 把内核的处理结果翻译回给用户

参照 Windows 更容易理解。操作 Windows 时同样不直接接触内核,而是通过图形界面:双击盘符、点开一个程序。Shell 在 Linux 中占据的就是图形界面那个位置,区别在于它接收的是文字,不是鼠标点击。
桌面界面如此直观,实际工作中却几乎都在使用命令行,原因在于图形界面把每一步操作都固定成了一个人工动作,而命令行可以写进脚本、可以互相拼接、可以顺着网络传输。一行 ps aux | grep nginx 表达的意思,点开若干个窗口也未必凑得出来,凑出来了也没法自动重复。远程运维时的区别更明显:SSH 传的是一行文本,不是像素。
2.1 远程登录
通常的开发方式是用终端软件连到远程 Linux 主机。流程就三步:
bash
# 1. 在 Linux 主机上查自己的 IP
ifconfig
# 2. 在本地终端里连过去(ip 换成上一步看到的结果)
ssh 192.168.1.100
# 3. 提示输入用户名密码,输入即可登录
2.2 命令的通用形式
几乎所有 Linux 命令都是这个形式:
命令 [选项] [参数]
选项以 - 开头,可以合并写(ls -a -l 等价于 ls -al),参数则是命令作用的对象。这个结构不必刻意记忆,但掌握了它,查阅 man 手册时就知道该往哪里找。
另外三个热键值得提前熟悉,可以减少大量重复输入:
| 按键 | 作用 |
|---|---|
Tab |
命令补全和文件名补全。按一下没反应就按两下,会列出所有候选 |
Ctrl+C |
让当前正在跑的程序停下来 |
Ctrl+D |
表示键盘输入结束(EOF),也可以用来退出当前 Shell |
3. 路径:为什么每个文件都有唯一的名字
Linux 把磁盘上的文件和目录组织成一棵目录树,根是 /。这棵树有两个特点:
- 普通文件一定是叶子节点
- 目录可能是叶子(空目录),也可能是中间的路径节点
还有一条更重要的性质:任何一个节点都只有一个父节点 。从根出发定位某个文件,路径因此是唯一的。树状组织存在的理由就在这里:要能快速、唯一地定位到指定文件。
由此引出两种写法:
- 绝对路径 :从
/开始,不依赖当前所在位置 - 相对路径:相对于当前所处目录来定位
两者的适用场景不同。绝对路径不会因为用户位置的改变而丧失唯一性,所以配置文件里经常用绝对路径 ;相对路径写起来短,命令行里用得多。

写路径时有两个符号需要记住:. 表示当前目录,.. 表示上一级目录。~ 则代表当前用户的家目录。
4. 定位与移动:pwd、ls、cd
4.1 pwd:我在哪
bash
$ pwd
/home/jys/test
功能只有一个,但它是所有破坏性操作前成本最低的检查。执行 rm、mv 之前先运行 pwd,可以避免相当一部分误操作。
4.2 ls:观察目录
bash
ls # 列出当前目录
ls -a # 包括以 . 开头的隐藏文件
ls -l # 长格式:权限、链接数、所有者、组、大小、时间
ls -d # 把目录本身当文件显示,而不是列出它的内容
ls -i # 显示 inode 索引号
ls -F # 在文件名后加类型标记:* 可执行,/ 目录,@ 符号链接
ls -r # 反向排序
ls -t # 按修改时间排序
ls -R # 递归列出所有子目录
ls -1 # 一行只输出一个文件
-F 会加上类型标记,可以直接看出条目类型:
| 标记 | 含义 |
|---|---|
* |
可执行的普通文件 |
/ |
目录 |
@ |
符号链接 |
| ` | ` |
= |
套接字(socket) |
-a 和 -l 组合起来是最常用的:
bash
$ ls -al
total 12
drwxrwxr-x 3 jys jys 4096 Jan 11 15:01 .
drwx------ 22 jys jys 4096 Jan 11 14:21 ..
drwxrwxr-x 2 jys jys 4096 Jan 11 14:22 dir
-rw-rw-r-- 1 jys jys 0 Jan 11 14:22 file.txt
4.3 读懂 ls -l 的输出
这一行的信息密度很高,值得逐段拆开。后面讲权限时会一直用到它:
-rwxr-xr-- 1 owner group 4096 Sep 16 15:30 index.html
│ │ │ │ │ │ │
│ │ │ │ │ │ └── 文件名
│ │ │ │ │ └── 最后修改时间
│ │ │ │ └── 文件大小(字节)
│ │ │ └── 所属组
│ │ └── 所有者
│ └── 硬链接数
│
└── 文件类型 + 权限
│
├─ - 文件类型:普通文件
│
└─ rwxr-xr--
├─ rwx 所有者 (owner):读 ✓ 写 ✓ 执行 ✓
├─ r-x 所属组 (group):读 ✓ 写 ✗ 执行 ✓
└─ r-- 其他人 (others):读 ✓ 写 ✗ 执行 ✗
从左到右依次是:文件类型与权限位、硬链接数、所有者、所属组、大小(字节)、最后修改时间、文件名。开头那十个字符里,第一个是文件类型,剩下九个分三组,分别是所有者、组、其他人的权限。
4.4 cd:改变工作目录
bash
$ cd .. # 回到上一级
$ cd /home/jys/test # 绝对路径
$ cd ../temp/ # 相对路径
$ cd ~ # 直接回家目录
$ cd - # 回到最近一次待过的目录
cd - 用于在两个目录之间来回切换:
bash
$ pwd
/home/jys
$ cd /
$ cd - # 回到 /home/jys
$ cd - # 又回到 /
顺带一个容易忽略的约定:每新建一个用户,系统会在 /home 下创建以用户名命名的工作目录;而 root 用户在安装系统时就已经内置了工作目录 /root。
bash
$ ls /home
jys litao wangwu
5. 创建与删除:touch、mkdir、rmdir、rm
5.1 touch:创建文件,或拨动时间
bash
touch newFile.txt # 文件不存在就创建空文件
touch -a newFile.txt # 只改访问时间
touch -m newFile.txt # 只改修改时间
touch 看似简单,背后却是文件系统里一个常被忽略的设计:每个文件有三个时间戳。用 stat 可以看得很清楚:
bash
$ stat newFile.txt
File: 'newFile.txt'
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd01h/64769d Inode: 1844719 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1003/ jys) Gid: ( 1003/ jys)
Access: 2023-01-11 15:06:45.832787572 +0800
Modify: 2023-01-11 15:06:45.832787572 +0800
Change: 2023-01-11 15:06:45.832787572 +0800
Birth: -
- Access(atime):最后一次读取文件内容的时间
- Modify(mtime):最后一次修改文件内容的时间
- Change (ctime):最后一次修改文件元数据的时间
这里有一个容易被忽略的地方。执行 touch -a 只修改访问时间,但 Change 也会随之改变:
bash
$ touch -a newFile.txt
$ stat newFile.txt
Access: 2023-01-11 15:07:00.062846804 +0800 # 变了
Modify: 2023-01-11 15:06:45.832787572 +0800 # 没变
Change: 2023-01-11 15:07:00.062846804 +0800 # 也变了
原因在于 Change 记录的是 inode 本身的变更。你修改了 atime,atime 是存在 inode 里的,所以 inode 被改动了,ctime 自然要更新。mtime 和 ctime 不是一回事:前者是内容,后者是元数据,这个区分在排查问题时经常用得上。
5.2 mkdir:创建目录
bash
mkdir mydir # 创建单个目录
mkdir -p path1/path2/path3/path4 # 级联创建,父目录不存在会自动建
-p 很实用。没有它,mkdir a/b/c 在 a 不存在时会直接报错;有它,整条路径会被一次性建好。
bash
$ mkdir -p path1/path2/path3/path4
$ tree path1
path1
└── path2
└── path3
└── path4
tree 不是系统自带的,需要单独装。root 用户直接 yum install -y tree,普通用户加 sudo 前缀。
5.3 rmdir:只能删空目录
bash
rmdir mydir # 删除空目录
rmdir -p path1/path2/path3/path4 # 连同变空的父目录一起删
它的限制很明确:目录非空时无法删除。
bash
$ touch path1/myfile.txt
$ rmdir -p path1/path2/path3/path4
rmdir: failed to remove directory 'path1/path2': Directory not empty
5.4 rm:删除工具
bash
rm file.txt # 删除文件
rm -i file.txt # 删除前询问
rm -r dir # 递归删除目录及其内容
rm -f file.txt # 强制删除,不询问
rm -rf path1 # 递归强制删除,几乎不问
对比一下 rm 和 rmdir 的行为差异,能看出一致的设计取向:
bash
$ rm dir
rm: cannot remove 'dir': Is a directory # 不加 -r 删不了目录
$ rm -r dir # 加 -r 才行
$ rm -f path1
rm: cannot remove 'path1': Is a directory # -f 也不能替代 -r
$ rm -rf path1 # 两个都需要
-r 和 -f 是两件事:-r 管递归,-f 管是否询问和是否忽略只读属性。它们不能互相替代。
rm -rf 需要单独提醒。它几乎不会给出任何确认机会,而 Linux 下没有回收站,删除后不可恢复。使用之前务必用 pwd 确认位置、用 ls 确认内容,并尽量避免与通配符一起使用。
6. 复制与移动:cp、mv
6.1 cp:复制
bash
cp myfile.txt myfile-backup.txt # 复制并改名
cp *.txt dir/ # 通配符匹配多个文件,复制到目录
cp -r source_dir target_dir # 递归复制目录
cp -i a.txt backup/a.txt # 覆盖前询问
cp -f a.txt backup/a.txt # 强制覆盖,不询问
* 是通配符,匹配任意长度的字符;? 匹配单个字符。cp a*.txt dir/ 会把所有以 a 开头的 .txt 文件一起复制过去。
默认行为要注意:如果目标文件已存在,cp 会直接覆盖,不问一声。
bash
$ echo "hello jys" > myfile.txt
$ cp myfile.txt myfile-backup.txt
$ cat myfile-backup.txt
hello jys # 原内容被无声覆盖了
要避免这种沉默覆盖,用 -i:
bash
$ cp -i myfile.txt myfile-backup.txt
cp: overwrite 'myfile-backup.txt'? y
6.2 mv:移动或重命名
mv 是 move 的缩写,但它的行为取决于第二个参数是什么:
- 第二个参数是文件名:完成重命名,源只能有一个
- 第二个参数是已存在的目录:把源移动进去,源可以有多个
bash
mv myfile.txt yourfile.txt # 重命名
mv myfile.txt temp/ # 移动到目录
mv temp ../ # 目录也能移动
mv -i a.txt b.txt # 目标存在时询问
重命名时同样有个陷阱:如果目标名已存在,改名等于覆盖。
bash
$ touch myfile.txt yourfile.txt
$ mv yourfile.txt myfile.txt # myfile.txt 的内容被 yourfile.txt 顶掉了
在意的话,同样用 -i 让系统先问一句。
7. 读文件:cat、more、less、head、tail
7.1 cat:一次性输出
bash
cat temp.txt # 输出全部内容
cat -b temp.txt # 非空行编号,空行不编号
cat -n temp.txt # 所有行编号,空行也编号
cat -s temp.txt # 多行连续空行压缩成一行
-b 和 -n 的区别只在空行上,但差别很直观。假设文件中间有两行空行:
bash
$ cat -b temp.txt
1 hello jys
2 hello jys
3 hello jys
4 hello jys # 空行被跳过,不占编号
5 hello jys
$ cat -n temp.txt
1 hello jys
2 hello jys
3 hello jys
4 # 空行照样占一个编号
5
6 hello jys
7 hello jys
-s 则用来收拾混乱的空行:
bash
$ cat -s temp.txt # 连续五个空行会被压成一个
7.2 more 与 less:分页查看
cat 的问题在于把整个文件一次性输出到终端。用 cat 打开几百 MB 的日志,终端会长时间无响应。
more 能分页,但只能向后翻,无法回看 。less 才是 Linux 中通用的查看工具:
bash
less -N app.log # 显示行号
less -i app.log # 搜索时忽略大小写
less 的优势在于交互。进入之后:
| 操作 | 作用 |
|---|---|
/字符串 |
向下搜索 |
?字符串 |
向上搜索 |
n |
重复上一次搜索 |
N |
反向重复上一次搜索 |
PageUp / PageDown |
前后翻页 |
q |
退出 |
还有一个工程上的优势:less 在查看之前不会加载整个文件。打开几十 GB 的日志时,这一点决定了它是否可用。
7.3 head 与 tail:取头取尾
bash
head temp.txt # 默认前 10 行
head -5 temp.txt # 前 5 行
tail temp.txt # 默认后 10 行
tail -3 temp.txt # 后 3 行
tail -f app.log # 持续跟踪追加内容,Ctrl+C 退出
tail -f 在运维中使用频率极高,它让文件末尾的新内容实时显示在屏幕上,查看日志基本依靠它。
7.4 组合起来解决实际问题
有一个经典问题:如何显示文件的第 180 到 200 行?
单个命令无法完成,但把两个组合起来就可以:
bash
$ head -200 temp.txt | tail -20
hello 180
hello 181
...
hello 199
先取前 200 行,再从这 200 行里取最后 20 行,落下来的正好是第 180 到 200 行。这里的 | 是管道,它把左边命令的输出直接交给右边命令当输入。
每个命令只做一件事,剩下的交给组合。这是 UNIX 留下来的思路,后面 grep、tail -f 用的是同一个方法。
8. 查找与别名
8.1 find:在目录树里找文件
bash
find ~ -name test.c # 在家目录下按名字找
find . -name '*.c' # 当前目录及子目录下所有 C 文件
find /var/log -type f -mtime -1 # 一天内修改过的普通文件
find 会实际遍历目录树并访问磁盘,在大文件系统上可能很慢。如果需要在几十 GB 的磁盘上运行,通常会把它放到后台执行。
8.2 which 与 whereis:定位程序
两个命令容易混,区别在于找的范围:
bash
$ which ls
alias ls='ls --color=auto'
/usr/bin/ls
$ whereis ls
ls: /usr/bin/ls /usr/share/man/man1/ls.1.gz /usr/share/man/man1p/ls.1p.gz
which 回答「当前 PATH 里会执行哪个程序」,whereis 回答「这个程序相关的文件都散落在哪」------二进制、源码、手册页一起列出来。
8.3 alias:给命令起短名
bash
alias hello='ls -a -l -n' # 定义别名
alias ll='ls -alF' # 最常用的一个
which hello # 可以看到别名确实生效了
unalias hello # 取消别名
注意 which 的输出会先显示别名定义、再显示真实路径,这说明 Shell 在查找命令时是先看别名、再看 PATH。
别名默认只在当前 Shell 会话中有效。需要长期生效时,写进 ~/.bashrc,然后执行 source ~/.bashrc 重新加载。
8.4 grep:在文件里找内容
find 找的是文件,grep 找的是文件里的行:
bash
grep "abcd" temp.txt # 基本查找
grep -i "abcd" temp.txt # 忽略大小写
grep -n "abcd" temp.txt # 输出行号
grep -v "abcd" temp.txt # 反向选择:输出不含该字符串的行
假设文件内容是 abcd / ABCD / hello / jys / 1234 五行,几个选项的效果对比就很清楚了:
bash
$ grep -n "abcd" temp.txt
1:abcd
$ grep -ni "abcd" temp.txt
1:abcd
2:ABCD
$ grep -v "abcd" temp.txt
ABCD
hello
jys
1234
$ grep -vni "abcd" temp.txt
3:hello
4:jys
5:1234
选项可以叠加,-vni 就是「反向 + 显示行号 + 忽略大小写」三合一。
9. 时间与系统信息
9.1 date:格式化时间与时间戳
bash
date # Wed Jan 11 18:22:50 CST 2023
date +%Y/%m/%d # 2023/01/11
date +%Y/%m/%d-%H:%M:%S # 2023/01/11-18:23:20
常用的格式标记:
| 标记 | 含义 |
|---|---|
%Y |
完整年份(0000...9999) |
%m |
月份(01...12) |
%d |
日(01...31) |
%H |
小时(00...23) |
%M |
分钟(00...59) |
%S |
秒(00...61) |
%F |
等价于 %Y-%m-%d |
%X |
等价于 %H:%M:%S |
时间戳是另一块常用功能:
bash
$ date +%s # 1673432636
$ date +%Y/%m/%d-%H:%M:%S -d @0
1970/01/01-08:00:00
$ date +%Y/%m/%d-%H:%M:%S -d @1000000000
2001/09/09-09:46:40
Unix 时间戳的定义是:从 1970 年 1 月 1 日 UTC 午夜开始所经过的秒数,不考虑闰秒 。上面 @0 显示成 08:00:00 而不是 00:00:00,是因为系统时区是东八区。
date -s 能设置系统时间,但只有 root 有权限,普通用户只能查看。
9.2 cal:日历
bash
cal # 当前月
cal 1949 # 某一年
cal -3 # 前一个月、当前月、下一个月
cal -y # 当前整年
cal -j # 显示一年中的第几天
9.3 系统信息与资源
bash
uname -a # 内核名称、主机名、版本、硬件名、处理器类型等
lsb_release -a # 发行版信息
top -d 1 -n 5 # 每隔 1 秒刷新,共刷新 5 次
top 默认会一直刷新,按 q 退出。-d 控制刷新间隔,-n 控制刷新次数。加上这两个参数,它就能被脚本调用了。
浮点运算可以交给 bc:
bash
$ echo 'scale=4; 10 / 3' | bc
3.3333
9.4 关机
bash
shutdown -h now # 停掉服务后立即关机
shutdown -r now # 重启
shutdown -t 60 # 60 秒后关机
10. 打包与压缩
这两个概念经常混淆,但它们实际上是两步:
- 打包:把一堆文件合成一个文件,体积基本不变
- 压缩:用算法把体积变小
tar 首先是打包工具,再通过选项去调用压缩程序。
10.1 tar 的参数速记
bash
tar -cvf archive.tar dir/ # 只打包,不压缩
tar -zcvf archive.tar.gz dir/ # 打包并用 gzip 压缩
tar -jcvf archive.tar.bz2 dir/ # 打包并用 bzip2 压缩
tar -ztvf archive.tar.gz # 不解包,直接看内容
tar -zxvf archive.tar.gz -C /tmp # 解压到指定目录
参数含义:
| 参数 | 作用 |
|---|---|
-c |
创建归档(create) |
-x |
解开归档 |
-t |
查看归档内的文件列表 |
-z |
用 gzip 压缩 / 解压 |
-j |
用 bzip2 压缩 / 解压 |
-v |
显示处理过程 |
-f |
指定归档文件名,后面必须紧跟文件名 |
-C |
解压到指定目录 |
-f 的位置是新手最容易出错的地方:它后面必须立即接文件名,中间不能再插别的参数。
打包时如果用了绝对路径,tar 会打印一句警告:
tar: Removing leading `/' from member names
这是刻意为之:归档里的路径会自动去掉开头的 /,免得解压时覆盖系统原有文件。
10.2 只解压其中一个文件
tar 允许你从归档里挑文件解出来。先用 -t 看清结构:
bash
$ tar -ztvf /tmp/etc.tar.gz
...
etc/passwd
...
然后指定路径解压:
bash
$ tar -zxvf /tmp/etc.tar.gz etc/passwd
注意路径写的是 etc/passwd,不带开头的 /,原因就是上面那条警告。
10.3 zip 与 unzip
跨平台交换文件时,ZIP 格式兼容性更好:
bash
zip -r project.zip project/ # 递归压缩目录
unzip project.zip -d /tmp # 解压到指定目录
如果需要在 Windows 和 Linux 之间传文件,rzsz 工具能让上传下载变成拖拽操作。
11. 权限:Linux 的安全模型
前面讲了那么多命令,但真正决定「谁能对什么做什么」的,是权限系统。
11.1 两种用户
Linux 下的用户分成两类:
- 超级用户 root:可以做任何事,不受限制
- 普通用户:只能做有限的事
提示符直接表明了身份:# 是 root,$ 是普通用户。这个视觉线索很实用,能让你时刻清楚自己当前持有的权限。
bash
su # 切换到 root(省略用户名)
su litao # 从 root 切回普通用户
11.2 三种人,三种权限
权限是围绕「人」和「事」两个维度组织的。
先说人。访问者分三类:
| 符号 | 身份 |
|---|---|
u |
User,文件的所有者 |
g |
Group,所有者所在的组 |
o |
Others,其他用户 |
再说事。基础权限就三个:
| 权限 | 权重 | 对文件 | 对目录 |
|---|---|---|---|
r |
4 | 读取文件内容 | 浏览目录内的信息 |
w |
2 | 修改文件内容 | 删除、移动目录内的文件 |
x |
1 | 执行文件 | 进入目录(cd) |
同样一个 w,落在文件和目录上完全是两件事,最后一列尤其要看清楚。粘滞位就是从这个差别里长出来的。
不具某项权限时,用 - 占位。
11.3 权限的两种写法
九位权限位,每三位一组,每组权重固定是 4、2、1:

rwxr-xr-- 换算成数字就是:u = 4+2+1 = 7,g = 4+1 = 5,o = 4,合起来 754。
反过来,看到 644 就知道是 rw-r--r--。
11.4 文件类型也在第一位
ls -l 开头那个字符不只表示权限,第一位是文件类型:
| 字符 | 类型 |
|---|---|
- |
普通文件 |
d |
目录 |
l |
软链接(类似 Windows 快捷方式) |
b |
块设备(硬盘、光驱等) |
c |
字符设备(屏幕等串口设备) |
p |
管道文件 |
s |
套接口文件 |
无法确定文件的类型时,可以交给 file 命令,它按内容判断,不依赖扩展名:
bash
$ file image.bin
11.5 chmod:改权限
有两种写法,对应两种使用习惯。
数字写法适合一次性设定完整权限:
bash
chmod 664 abc.txt # rw-rw-r--
chmod 640 abc.txt # rw-r-----
chmod 755 run.sh # rwxr-xr-x
chmod -R 750 private/ # 递归修改目录及内容
符号写法适合增量修改:
bash
chmod u+w abc.txt # 给所有者加写权限
chmod o-x abc.txt # 去掉其他人的执行权限
chmod a=x abc.txt # 所有人的权限都设为仅执行
符号写法的结构是「用户符号 + 操作符 + 权限字符」:
- 用户符号:
u(所有者)、g(同组)、o(其他人)、a(所有人) - 操作符:
+增加、-取消、=直接赋值
一个限制要记住:只有文件的拥有者和 root 才能修改文件权限。
11.6 chown、chgrp、umask
bash
chown user1 f1 # 改所有者
chown -R user1 filegroup1 # 递归改所有者
chgrp users /abc/f2 # 改所属组
chgrp -R users dir/ # 递归改所属组
umask 是最容易被忽略的一个,它决定了新建文件默认拿到什么权限。
新建文件的基础权限是 0666,新建目录是 0777。但实际创建出来往往不是这个值,因为还要被 umask 削一刀:

规则是:基础权限 & ~umask (取反再按位与)。umask 里为 1 的位,会把对应权限位清零。
bash
umask # 查看当前掩码
umask 044 # 设置掩码
超级用户默认掩码是 0022,普通用户默认 0002。所以 root 新建的目录通常是 755,文件是 644。
11.7 sudo:按需分配权力
root 权限强大也危险。更好的做法是让普通用户按需借用特定权限,配置文件是 /etc/sudoers:
# 格式:接受权限的用户 登陆的主机 = (执行命令的用户) 命令
配置好之后:
bash
sudo -u root /usr/sbin/useradd u2
这样普通用户就能在授权范围内执行特定命令,而不是拿到完整的 root 权限。
12. 一个反直觉的结论:删除文件取决于目录权限
先看目录的三个权限分别意味着什么:
- 没有
x:无法cd进这个目录 - 没有
r:无法用ls查看目录内容 - 没有
w:无法在目录里创建文件,也无法删除文件
前三点里,第三点可以推出一个不太合理的结论:只要用户对目录有 w 权限,就能删除目录中的任何文件,无论他对该文件本身是否具有写权限。
自己创建的文件,他人却能删除。这在实践中确实会发生:
bash
$ chmod 0777 /home/
$ ls -ld /home/
drwxrwxrwx. 3 root root 4096 9月 19 15:58 /home/
$ touch /home/root.c # root 建一个文件
$ su - litao # 切成普通用户 litao
$ rm /home/root.c # litao 把 root 的文件删了
为什么会这样?因为删除文件修改的是它所在的目录,而不是文件本身。所谓删除,就是从目录项中移除一个名字。起决定作用的是目录的 w 权限,与文件自身的权限无关。
这个设计在共享目录里就是灾难。而 Linux 的解法是粘滞位:
bash
$ chmod +t /home/ # 加上粘滞位
$ ls -ld /home/
drwxrwxrwt. 3 root root 4096 9月 19 16:00 /home/
权限位末位变成 t。再看效果:
bash
$ su - litao
$ rm /home/root.c
rm: 无法删除"/home/root.c": 不允许的操作

加上粘滞位之后,目录里的文件只能由三种人删除:
- 超级管理员
- 该目录的所有者
- 该文件的所有者
/tmp 是粘滞位最典型的应用场景:它必须让所有人可写,但又不能让任何人删掉别人的东西。
12.1 权限问题的排查顺序
理解了上面的机制,排查权限问题时就有了固定顺序:
- 先看目录,再看文件 。目录没有
x就进不去,此时文件权限再宽松也没用 - 区分
r和x对目录的不同含义 。只有r没有x,你能看到目录里的文件名,但读不了内容、也进不去 - 删除操作看父目录,不看文件本身
第 2 条最容易出错:误以为有读权限就能进入目录。实际上能否在目录下操作,门槛是 x 而不是 r。
13. 常见误区清单
把前面提到的易错点集中列出:
- 用
cat打开大文件 。几百 MB 的日志会使终端长时间无响应,应改用less - 混淆
>和>>。前者覆盖,后者追加 - 用
rm -f删除目录 。删除目录必须用-r,-f不能替代 - 以为
rm有回收站 。Linux 下删除后不可恢复,执行rm -rf之前先pwd和ls - 复制目录时漏掉
-r。cp不加-r遇到目录会直接报错 - 以为
cp和mv会提示覆盖 。默认不提示,需要提示时加-i tar中-f后面插入了其他参数 。-f必须紧跟文件名- 混淆 mtime 和 ctime。前者记录内容,后者记录元数据
- 以为有目录的
r权限就能进入 。真正决定能否进入的是x - 只看文件权限就判断能否删除 。删除由父目录的
w决定
结语
这批指令不必当作单词表来背。它们串在同一条线索上:目录树给每个文件一个唯一的位置,路径让你能精确定位到它,Shell 把你的意图翻译成内核可以执行的动作,权限决定你能否真正操作。这条线索理清了,命令本身反而容易记,因为每条命令要么在操作这棵树,要么在与这套权限打交道。