Linux 教程新手篇(草稿)
在做实验,需要每五分钟去调整一下仪器,但中间的五分钟有点无事可做。 所以来写一下这个东西得了。
原本是想要写个给同专业的同学看的教程,写了一点觉得,诶,我为啥不写成更通用的教程呢。 于是就这样做吧。
顶层设计
我的想法是这样的:按照两个部分去写,一个新手篇,一个进阶篇。 不管哪个部分,内容都不会限制于 Linux,甚至 Linux 的内容可能只占一半。 其实很多人并不是不会用 Linux 而是对缺少对操作系统的一般认识, 就像看到自己电脑有 C D E F 盘,就以为电脑里真的插了四个硬盘一样。 另一个常见的误区就是不区分内存和外存。类似此类的问题。 要真的只是某个 Linux 上的问题不会,反而还都是少见的。
暂时的计划是这样的,新手篇的内容包含:
- 什么是 shell?(和 windows 的桌面一个作用,只是变成了敲命令)
- shell 里命令的格式是怎样的?(第一个是要执行的程序,之后的是参数)
- 常用的几个命令。 说白了就是围绕 bash 介绍一些基础用法。 这其实是相当繁琐的事情,但又是很基础的,不得不介绍,新手接触 Linux 的话也不可能绕过。
进阶篇的内容会多一些杂一些,包括:
- 冯诺依曼架构(计算机的硬件组成,至少把内存和外存分清楚吧)
- 操作系统的结构(内核和用户态程序的区别)
- 分区、文件系统、挂载,这些概念是指什么;linux常见的几个文件夹里放什么。
- 程序和进程间通信(环境变量,信号,输入输出,返回值)
- shell 中一些内置命令,以及重定向、通配符等。
- 常用的工具(shell 中常用的外置命令)。
- 用户、文件所有者和权限。
- 网络协议,NAT。
- 编程时常用的概念(编译、链接、解释器等)。
在这之后就区分“专业”了,例如做科学计算的话,需要了解:
- 常用的并行计算协议(MPI,OpenMP,CUDA,OpenCL,SYCL)。这里其实我也缺一些知识,我需要自己先把自己的疑惑解决了再写。
- 队列系统。但是我已经写过了。
当然也可以写一些其它的。但这些要么已经有人写过了写得很好我受益匪浅,我觉得不需要我来写(例如 nix),要么我自己也不熟(例如 docker)。 总之,写个屁。
我想要达成的目标是这样的:
- 对于普通用户:
- 读完新手篇,就可以着手做计算了。
- 读完进阶篇,就大致知道自己遇到常见的错误时如何排查,或者如何向别人描述问题了。
- 对于管理员:
- 全读完,就可以开始学习怎么搭服务器了(遇到问题时,知道去哪里找答案了)。
不只是做计算,如果有想自己折腾 Linux 的,这也算是一个比较好的入门;但我会侧重于前者,主要是后者的话,场景太杂,我根本不知道该写啥。
所以说应该会写四个部分:
- 速查篇,列个表格,满足某些人“我要一口吞个大象”的心态,他吞不下是他的问题,我给他把大象放到这里。
- 新手篇,口语化地带领读者实践一圈。
- 进阶篇,讲基础的、常见的、重要的、但是又往往被忽略的概念,为了给进一步向前走做准备。
- 专业篇,指科学计算,这一部分更偏向于我自己的整理而不是科普给别人。
那就开始吧
下面是新手篇。
那个黑框框里是什么?
先考虑这个问题:在 Windows 电脑上,如何删除一个文件?步骤是这样的:
- 启动资源管理器(就是“我的电脑”或者“此计算机”)。
- 打开你要删除的文件所在的文件夹。
- 右键点击文件,选择删除。 在这个过程中,“资源管理器”或者“exploer.exe”承担了这样一个“传话筒”的角色: 它接受你下达的指令(打开某个文件夹,删除某个文件,等),并将这个指令传达给系统中那些更底层的程序(即内核)去执行。
通过 PuTTY 或者别的什么程序连接到服务器后打开的那个黑框框,就是 Linux 系统中的“资源管理器”[^2],它的名字叫“bash”[^3]。 它的作用与 Windows 的资源管理器是一样的:你可以给他传达删除某个文件、启动某个程序等指令,它就会把这个指令传达给系统的更底层去实际执行。 学习 Linux 的第一步,就是学习如何给 bash 下达指令,例如打开某个文件夹、删除某个文件、启动某个程序等。 至于其中的细节(例如 bash 是如何与内核交互的,为了删除一个文件内核又需要做什么),暂时不需要关心。
类似于 Windows 的资源管理器或 bash 这样的程序被称为“shell”,也就是“壳”, 这是指它把系统中复杂的细节包装了起来,让用户可以简单地进行删除文件、打开文件夹等操作,大多情况下不用关心细节。
几个常见的命令
学习英语的第一步并不是钻研语法,而是学习最常用的几句话大致怎么说。学习 bash 也是一样的。 下面是几个最常用的命令,你可以跟着操作一遍,就大致知道这些命令该怎么用了,同时也可以理解 Linux 上一些基本的概念。
pwd
在 Windows 的资源管理器中,可以在窗口上部、标题栏下方的地址栏中看到现在打开的是哪个文件夹(称为当前的“工作目录”);
而在 bash 中,可以使用 pwd
命令查看工作目录的路径(pwd
是“print working directory”的缩写):
pwd
将 pwd
输入到黑框框中,然后按回车键,它就会输出一行文字,告诉你现在在哪个文件夹下。例如,它输出的结果可能是:
/home/chn
这表示你现在打开的是“根目录”下的 home
文件夹下的 chn
文件夹。
要解释“根目录”的意思,需要先提到一个 Linux 与 Windows 的不同之处: Windows 中往往逻辑上有多个“磁盘”(C 盘、D 盘、E 盘等),而 Linux 中逻辑上只有一个“磁盘”,这个“磁盘”就是所谓的“根目录”。 当然,这个“根目录”中的内容完全可能实际是分布在多个硬盘上存储的(例如,某几个文件夹在一个硬盘上,另外几个文件夹在另一个硬盘上), 但这些细节初学者并不需要关心,这些文件实际上存储在哪里对初学者来说操作起来完全没有区别。
你还会发现另外一个区别:在 Windows 中,文件夹之间的分隔符是反斜杠 \
,而在 Linux 中,文件夹之间的分隔符是斜杠 /
。
还有第三个重要的区别你可能没有发现:
在 Windows 中,文件和文件夹名字是不区分大小写的,也就是说无法在同一个文件夹下新建 aaa.txt
和 AAA.txt
两个文件;
而 Linux 中文件和文件夹名是区分大小写的,aaa.txt
和 AAA.txt
完全可以同时存在。
不仅仅是文件名,整个 Windows 的习惯都是不区分大小写的,而整个 Linux 的习惯则都是区分大小写的。
总之,我们之后学习的所有命令、各种参数等,它们都是区分大小写的;
如果你在哪里看到过某某命令不区分大小写的说法,那大概率是 Windows 的特色,不能不经确认套用到 Linux 上。
ls
使用 ls
命令(“list”的缩写)列出当前目录下,名字不以 .
开头的文件和文件夹(也就是不隐藏的文件和文件夹):
ls
在 Windows 上,一个文件或者文件夹是否是隐藏的,是它的一个属性(右击-属性中,可以修改);
但在 Linux 上,通常直接把名字以 .
开头的文件或文件夹称为“隐藏文件”或“隐藏文件夹”,并通常用来存放程序的设置等用户平时不需要修改的内容。
隐藏的文件与不隐藏的文件本质上并没有什么不同,这只是一种习惯性的用法。
要列出当前目录中所有的文件和文件夹(包括隐藏的),可以使用 ls -a
命令(“a”是“all”的缩写):
ls -a
你一定可以看到两个特殊的文件夹:一个名字叫 .
,令一个叫 ..
。我稍后会介绍它们是什么。
要列出别的地方的文件夹里的内容(而不是当前文件夹),可以在 ls
命令后面加上这个文件夹的路径,例如:
ls /
这会列出根目录下的文件和文件夹(不包括隐藏的)。
ls /home/chn
这会列出 home
文件夹下的 chn
文件夹中的文件和文件夹(不包括隐藏的)。
当然,如果你没有权限看这个文件夹里的内容的话,这个命令就会报错,但也不会有什么坏处,也不会影响之后的命令。
你可以按照自己的兴趣,随意列出几个文件夹的内容,探索一下服务器上有什么东西。不会把服务器搞坏的,不用担心。
也可以将 ls
列出来的东西与 WinSCP 对照者看一下,确认它们是一样的[^4]。
/nix/store
或者 .node_modules
的文件夹的内容,你的 shell 可能会卡死,这只是因为里面的文件太多了。
你可以耐心等待它列完,也可以按 Ctrl + C
来终止这个命令,实在不行就重启 PuTTY。
mkdir
使用 mkdir
命令(“make directory”的缩写)在当前目录下创建一个新的文件夹:
mkdir test
这样就在当前目录下创建了一个名为 test
的文件夹。
cd
使用 cd
命令(“change directory”的缩写)切换当前目录(也就是,打开别的文件夹):
cd test
这样就进入了当前目录下的 test
文件夹。
如果要返回上一级目录,可以使用 cd ..
命令:
cd ..
还记得前面提到的 .
和 ..
这两个特殊的文件夹吗?其实 ..
就是指上一层文件夹,而 .
就是指同一层文件夹。
例如,下面几个目录的写法,它们之间是完全等价的:
/home/chn
/home/chn/.
/home/chn/./././././.
/home/../home/chn
/home/chn/../../home/chn
/home/././chn/.././chn
目前为止,很多命令都需要用到文件或文件夹的路径。 实际上,文件或者文件夹的路径有两种写法,大多数情况下(包括基础篇中提到的所有命令但不仅限于),两种写法都可以,完全等价:
- 直接写当前目录下的文件或文件夹的名字,例如
cd test
或者cd ./test
或者cd ./test/../test
等,这种写法称为相对路径; - 写完整的路径(以
/
开头的路径),例如cd /home/chn/test
,这种写法称为绝对路径。 但也存在一些例外,其中一个重要的例外在基础篇的最后会提到。
echo
使用 echo
命令输出一行文字:
echo Hello
这样就输出了一行 Hello
。——这有什么用呢?
可以使用下面这个命令,在当前目录下创建一个名为 file.txt
的文件,并在这个文件中写入一行 Hello
(如果这个文件之前已经存在,其中的内容会被覆盖):
echo Hello > file.txt
如果你想在这个文件中追加一行 World
(之前的内容不会被覆盖),可以使用下面这个命令:
echo World >> file.txt
这里的 >
和 >>
是两个特殊的符号,被称为“重定向符号”,
它表示把之前的命令的标准输出(也就是,原本会输出到屏幕上的内容[^5])重定向到某个文件中。
>
表示覆盖,>>
表示追加。
它们的用法不仅限于 echo
命令,而是可以用在任意的命令上,例如 ls > file.txt
就会把 ls
命令的输出重定向到 file.txt
文件中。
cat
使用 cat
命令(“meow meow meow”的缩写[^6])查看一个文件的内容:
cat file.txt
你当然可以“活学活用”,把 cat
命令的输出重定向到另一个文件中,这样就变相实现了文件的复制:
cat file.txt > file2.txt
类似于这样的“活学活用”在 Linux 中其实比比皆是。就像一篇光鲜亮丽的 sci 论文背后总是充满了科研现实的苟且。
cp
这个才是正经用来复制文件的命令(copy
的缩写):
cp file.txt file2.txt
如果要复制的是文件夹,需要加上 -r
参数(“r”是“recursive”的缩写[^7]):
cp -r test test2
rm
使用 rm
命令(“remove”的缩写)删除一个文件:
rm file.txt
这样就删除了当前目录下的 file.txt
文件。
要删除文件夹,需要加上 -r
参数(与 rm
相同):
rm -r test
这样就删除了当前目录下的 test
文件夹。
bash 的基本语法
按行分割
英语分为一个个句子,以句号作为结尾;输入给 bash 的指令是一条一条的,通常以换行作为结尾。 例如,输入下面的命令:
cd test
rm file.txt
cd ..
bash 会把它作为三条指令来执行。
命令与参数
英语的一句话中,主语后接的是谓语、宾语;
bash 的一条指令中,按照空格分隔,第一个单词是要执行的命令,之后如果还有内容的话,只要不是一些特殊的符号(例如 >
),就是这个命令的参数。
例如在下面的命令中:
rm file.txt
rm
是要执行的命令,file.txt
是这个命令的参数。
在按照空格分割的时候,连续的多个空格与单个空格是等价的,例如下面的命令:
rm file.txt
与上面的命令是等价的。
如果命令或者参数中包含空格,可以用双引号或单引号把它括起来,例如:
rm "file with space.txt"
这样 bash 就知道 file with space.txt
是传给 rm
的一个参数;而不是把它分成 file
、with
、space.txt
三个参数。
也就是说,要删除的是名为 file with space.txt
这一个文件,而不是名为 file
、with
、space.txt
的三个文件。
每个命令可以接受参数的个数不同。 例如,删除文件的命令通常需要至少一个参数(你得告诉它删除哪个文件),至多则不限制; 而复制文件的命令通常至少需要两个参数(你得告诉它将哪个文件复制到哪里)。
内置命令与外部程序
cd
、pwd
等命令并不会启动 bash 以外的程序:它们是 bash 内置的命令,它们的作用是调整或者输出 bash 本身的状态,
相当于查看或者修改 Windows 的资源管理器本身的设置,而不是要 bash 做什么实际的事情。
而 rm file.txt
这个命令则实际上是启动了一个放置在特定位置[^8]、名为 rm
的程序,并将 file.txt
作为参数传给它。
mkdir
、cp
等命令也是这样的。
那么,如果要运行一个程序,而这个程序又不放在类似于 /usr/bin
这样的特殊位置,应该怎么调用呢?有三个办法:
- 修改环境变量。新手篇不会介绍这个办法,因为新手很容易把环境变量搞乱。
- 使用绝对路径调用程序。例如,这个程序放置在
/home/chn
文件夹下,名字叫myprogram
,那么可以这样调用:/home/chn/myprogram
/home/chn/myprogram arg1 arg2
- 使用以
.
开头的相对路径调用程序。必须使用以.
开头的相对路径(..
也可),这就是之前提到的那个重要的例外。 例如,这个程序放置在当前文件夹下,名字叫myprogram
,那么可以这样调用:./myprogram
管道与 grep
命令
除了之前介绍的 >
和 >>
之外,还有一个非常常用的特殊符号,叫做“管道符号” |
,就是键盘上那个竖线。
它的作用是把一个命令的标准输出重定向到另一个命令的“标准输入”(就是这个命令原本看到的用户的输入[^9])。
管道符号经常和 grep
命令一起使用,grep
命令的作用是在输入中查找某个字符串,并输出包含这个字符串的行。
例如:
cat OUTCAR | grep "Total energy"
这个命令可以输出 OUTCAR
文件中包含 Total energy
的行。
grep
有一些常用的参数,包括:
-i
(“ignore case”的缩写):匹配时忽略大小写;-An
(“after”的缩写): 除了输出匹配的内容,还输出匹配内容的后几行;-Bn
(“before”的缩写): 除了输出匹配的内容,还输出匹配内容的前几行;-v
(“invert match”的缩写):输出不匹配的行,而不是输出匹配的行。
通配符
使用 *
来匹配路径中存在的文件或者文件夹。说起来抽象,举个例子就明白了。假定当前目录下有这样几个文件:
file1.txt
file2.txt
file3.txt
dir1
dir1/file.txt
dir2
dir2/file.txt
dir3
dir3/file.txt
那么下面这个命令:
myprogram *
等价于:
myprogram file1.txt file2.txt file3.txt dir1 dir2 dir3
下面这个命令:
myprogram dir*
等价于:
myprogram dir1 dir2 dir3
需要注意的是,*
是由 bash 负责解释和展开的,而不是由 myprogram
负责解释和展开的;
或者说,myprogram
从来不知道 *
的存在,它收到的信息已经是展开后的结果了。
另一个常见的通配符是 ~
,它表示当前用户的家目录(也就是你刚登陆时,所在的那个目录[^10])。例如:
myprogram ~/file.txt
可能等价于
myprogram /home/chn/file.txt
这个通配符也是由 bash 负责解释和展开的。 (等等,这个东西是通配符吗?)
for 循环,大括号展开
bash 也支持 for
循环,例如:
for i in 1 2 3; do mkdir $i; cd $i; cd ..; done
这个命令会在当前目录下创建三个文件夹,分别叫 1
、2
、3
,然后依次进入这三个文件夹,再依次返回到当前目录。
这里用到了 ;
来分隔多个命令,它的具体用法在新手篇里不会提到,仅在这一个特殊的例子中照葫芦画瓢写就可以了。
对于数值,bash 还支持大括号展开,例如:
[^1]: 这个更底层的程序就是所谓的“内核”。
[^2]: 无论是 Windows 上的资源管理器还是 Linux 上的这个“黑框框”,它有一个统一的名字,叫做“shell”,也就是“壳”,意思就是它把系统中复杂的细节包装了起来,让用户可以简单地进行删除文件、打开文件夹等操作。
[^3]: Linux 上最常见的是 bash,学校的超算用的就是这个;次常见的是 zsh,我管理的其它服务器使用的其实就是 zsh 而不是 bash。
这两个的基本使用方法是相同的,初学者不需要区分。
[^4]: WinSCP 默认不会列出隐藏的文件和文件夹,有时会误导新手以为 `.bashrc` 等文件不存在。
[^5]: 命令原本输出到屏幕上的内容不仅仅来自标准输出,还有标准错误输出(用于输出出错的信息,例如“permission denied”等)等途径。
如此使用重定向符号只会重定向标准输出,不会重定向标准错误输出等其它输出。
[^6]: 实际上是“concatenate”的缩写,但这个单词我,反正我不认识。
[^7]: 系统在实际复制一个文件夹时,总是需要首先新建文件夹本身,然后新建文件夹里的
删除文件夹里的内容,而这又需要首先删除文件夹里的文件夹里的内容,以此类推。
“recursive”就是“递归”的意思。单纯删除空文件夹的参数其实是 `-d`,意为“directory”,但这个参数很少用到,因为 `-r` 也可以删除空文件夹。
[^8]: 准确说,是 `PATH` 指向的目录。`rm` 等最常用的命令通常位于 `/usr/bin` 或 `/run/current-system/sw/bin`。
[^9]: 标准输入也不是程序读取用户输入的唯一途径。最常见的例外是一些需要读取密码的程序,为了防止密码泄漏,它们往往会从更底层直接拦截、读取掉。
这也是为什么命令行中输入密码时,大多情况下没有任何显示的原因。
[^10]: 准确说,是 `HOME` 环境变量的值。